Repository: DevToys-app/DevToysMac Branch: main Commit: 38d416083872 Files: 282 Total size: 22.5 MB Directory structure: gitextract_01cekmvj/ ├── .gitignore ├── CoreUtil/ │ ├── CoreUtil/ │ │ ├── Class/ │ │ │ ├── Action.swift │ │ │ ├── Delta.swift │ │ │ ├── NS+OnAwake.swift │ │ │ ├── NSColorView.swift │ │ │ ├── NSViewController+ChainObject.swift │ │ │ ├── NSViewController+StateObject.swift │ │ │ ├── Observable.swift │ │ │ ├── PipeOperator.swift │ │ │ ├── Query.swift │ │ │ ├── Reachability+Publisher.swift │ │ │ ├── Reachability.swift │ │ │ ├── RestorableData.swift │ │ │ ├── RestorableState.swift │ │ │ ├── Terminal.swift │ │ │ ├── ViewPlaceholder.swift │ │ │ └── Zip3Sequence.swift │ │ ├── CoreUtil.h │ │ ├── ExceptionHanlder/ │ │ │ ├── ExceptionHandler.swift │ │ │ ├── ExceptionHanlder.h │ │ │ └── ExceptionHanlder.m │ │ ├── Export.swift │ │ ├── Extensions/ │ │ │ ├── Combine/ │ │ │ │ ├── Combine+ObjectBag.swift │ │ │ │ ├── Combine+Peek.swift │ │ │ │ ├── NSControl+Combine.swift │ │ │ │ └── NSTextField+Combine.swift │ │ │ ├── CoreGraphics/ │ │ │ │ ├── Ex+CGPoint.swift │ │ │ │ ├── Ex+CGRect.swift │ │ │ │ └── Ex+CGSize.swift │ │ │ ├── Swift/ │ │ │ │ ├── Ex+Array.swift │ │ │ │ ├── Ex+Clamp.swift │ │ │ │ ├── Ex+Localize.swift │ │ │ │ ├── Ex+Number.swift │ │ │ │ └── Ex+OptionSet.swift │ │ │ └── UI/ │ │ │ ├── Ex+CALayer.swift │ │ │ ├── Ex+Image.swift │ │ │ ├── Ex+NSColor.swift │ │ │ ├── Ex+NSControl.swift │ │ │ ├── Ex+NSEdgeInsets.swift │ │ │ ├── Ex+NSEvent.swift │ │ │ ├── Ex+NSMenuItem.swift │ │ │ ├── Ex+NSPopover.swift │ │ │ ├── Ex+NSTableView.swift │ │ │ └── Ex+UI.swift │ │ ├── HotKey/ │ │ │ ├── HotKey.swift │ │ │ ├── Key.swift │ │ │ └── NSEvent+HotKey.swift │ │ └── Info.plist │ └── CoreUtil.xcodeproj/ │ ├── project.pbxproj │ └── project.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── IDEWorkspaceChecks.plist ├── DevToys/ │ ├── DevToys/ │ │ ├── App/ │ │ │ ├── AppViewController.swift │ │ │ └── AppWindowController.swift │ │ ├── AppDelegate.swift │ │ ├── Body/ │ │ │ ├── BodyViewController.swift │ │ │ ├── Coder/ │ │ │ │ ├── Base64DecoderView+.swift │ │ │ │ ├── HTMLDecoderView+.swift │ │ │ │ ├── JWTDecoderView+.swift │ │ │ │ └── URLDecoderView+.swift │ │ │ ├── Convert/ │ │ │ │ ├── DateConverterView+.swift │ │ │ │ ├── JSONYamlConverterView+.swift │ │ │ │ └── NumberBaseConverterView+.swift │ │ │ ├── Format/ │ │ │ │ ├── JSONFormatterView+.swift │ │ │ │ ├── SQLFormatterView+.swift │ │ │ │ └── XMLFormatterView+.swift │ │ │ ├── Generator/ │ │ │ │ ├── ChecksumGeneratorView+.swift │ │ │ │ ├── HashGeneratorView+.swift │ │ │ │ ├── LoremIpsumGeneratorView+.swift │ │ │ │ ├── QRCodeGeneratorView+.swift │ │ │ │ └── UUIDGeneratorView+.swift │ │ │ ├── Graphic/ │ │ │ │ ├── Icon Generator/ │ │ │ │ │ ├── Generators/ │ │ │ │ │ │ ├── AndroidIconGenerator.swift │ │ │ │ │ │ ├── IOSIconGenerator.swift │ │ │ │ │ │ ├── IcnsGenerator.swift │ │ │ │ │ │ ├── IcoGenerator.swift │ │ │ │ │ │ ├── IconFolderGenerator.swift │ │ │ │ │ │ ├── IconGenerator+Model.swift │ │ │ │ │ │ ├── IconsetGenerator.swift │ │ │ │ │ │ ├── IosIconGenerator.swift │ │ │ │ │ │ └── PngIconGenerator.swift │ │ │ │ │ ├── Icon Templete/ │ │ │ │ │ │ ├── IconImageManager.swift │ │ │ │ │ │ ├── IconSet.swift │ │ │ │ │ │ ├── IconTemplete+.swift │ │ │ │ │ │ └── IconTemplete.swift │ │ │ │ │ └── IconGeneratorView+.swift │ │ │ │ ├── Image Converter/ │ │ │ │ │ ├── Exporters/ │ │ │ │ │ │ ├── DefaultImageExporter.swift │ │ │ │ │ │ ├── HeicImageExporter.swift │ │ │ │ │ │ ├── WebpImageExporter.swift │ │ │ │ │ │ └── cwebp │ │ │ │ │ ├── ImageConverter.swift │ │ │ │ │ └── ImageConverterView+.swift │ │ │ │ ├── Image Optimizer/ │ │ │ │ │ ├── ImageOptimaizerView.swift │ │ │ │ │ ├── ImageOptimizer.swift │ │ │ │ │ ├── jpegoptim │ │ │ │ │ └── optipng │ │ │ │ ├── PDFGeneratorView+.swift │ │ │ │ └── QRCodeReaderView+.swift │ │ │ ├── HomeView+.swift │ │ │ ├── Media/ │ │ │ │ ├── Audio Converter/ │ │ │ │ │ ├── AudioConverter.swift │ │ │ │ │ ├── AudioConverterView+.swift │ │ │ │ │ └── AudioFileScanner.swift │ │ │ │ ├── Color Picker/ │ │ │ │ │ ├── Color.swift │ │ │ │ │ ├── ColorPickerView+.swift │ │ │ │ │ ├── Components/ │ │ │ │ │ │ ├── BoxHSBColorPicker.swift │ │ │ │ │ │ ├── BrightnessBarView.swift │ │ │ │ │ │ ├── CircleBarsHSBColorPicker.swift │ │ │ │ │ │ ├── CircleBoxHSBColorPicker.swift │ │ │ │ │ │ ├── CircleHueBarView.swift │ │ │ │ │ │ ├── ColorBoxView.swift │ │ │ │ │ │ ├── ColorPickerHandleLayer.swift │ │ │ │ │ │ ├── ColorSampleView.swift │ │ │ │ │ │ ├── HueBarView.swift │ │ │ │ │ │ ├── OpacityBarView.swift │ │ │ │ │ │ └── SaturationBarView.swift │ │ │ │ │ ├── Pixel Picker/ │ │ │ │ │ │ ├── ACOverlayController.swift │ │ │ │ │ │ ├── ACOverlayController.xib │ │ │ │ │ │ ├── ACOverlayPanel.swift │ │ │ │ │ │ ├── ACOverlayPreview.swift │ │ │ │ │ │ ├── ACOverlayWrapper.swift │ │ │ │ │ │ ├── ACPixelPicker.swift │ │ │ │ │ │ └── Utils/ │ │ │ │ │ │ ├── Ex+CGImage.swift │ │ │ │ │ │ ├── Ex+NSBezierPath.swift │ │ │ │ │ │ ├── Ex+NSColor.swift │ │ │ │ │ │ ├── ShowAndHideCursor.h │ │ │ │ │ │ ├── ShowAndHideCursor.m │ │ │ │ │ │ ├── ShowAndHideCursor.swift │ │ │ │ │ │ └── Util+PixelPicker.swift │ │ │ │ │ └── R+ColorPicker.swift │ │ │ │ └── Movie to Gif/ │ │ │ │ ├── GifConverter.swift │ │ │ │ └── GifConverterView+.swift │ │ │ ├── SettingView+.swift │ │ │ ├── Text/ │ │ │ │ ├── HyphenationRemoverView+.swift │ │ │ │ ├── JSON Search/ │ │ │ │ │ ├── JSONNormalSearchView.swift │ │ │ │ │ └── JSONSearchView+.swift │ │ │ │ ├── RegexTesterView+.swift │ │ │ │ ├── TextDiffView+.swift │ │ │ │ └── TextInspectorView+.swift │ │ │ └── Utils/ │ │ │ ├── CameraViewController.swift │ │ │ ├── FileConflictAvoider.swift │ │ │ ├── Identifier.swift │ │ │ ├── ImageDropper.swift │ │ │ ├── Slugify.swift │ │ │ └── ffmpeg/ │ │ │ ├── FFMpegExecutor.swift │ │ │ ├── FFProgressReport.swift │ │ │ ├── FFTask.swift │ │ │ ├── FFThumnailGenerator.swift │ │ │ ├── FFTime.swift │ │ │ └── ffmpeg │ │ ├── Bridging-Header.h │ │ ├── Component/ │ │ │ ├── Area.swift │ │ │ ├── Button.swift │ │ │ ├── CodeTextView.swift │ │ │ ├── ControlBackgroundLayer.swift │ │ │ ├── ControlContainer.swift │ │ │ ├── DatePicker.swift │ │ │ ├── DragImageView.swift │ │ │ ├── EmptyImageTableView.swift │ │ │ ├── FileDrop.swift │ │ │ ├── ImageDropView.swift │ │ │ ├── NumberField.swift │ │ │ ├── Page.swift │ │ │ ├── PopupButton.swift │ │ │ ├── RegexTextView.swift │ │ │ ├── Section.swift │ │ │ ├── SectionButton+.swift │ │ │ ├── SectionButton.swift │ │ │ ├── TagCloudView.swift │ │ │ ├── TextField.swift │ │ │ ├── TextFieldSection.swift │ │ │ ├── TextView.swift │ │ │ ├── TextViewSection.swift │ │ │ └── Toast.swift │ │ ├── DevToys.entitlements │ │ ├── Info.plist │ │ ├── Model/ │ │ │ ├── AppModel.swift │ │ │ ├── Settings.swift │ │ │ ├── Tool+Default.swift │ │ │ ├── ToolCategory+Default.swift │ │ │ └── ToolManager+.swift │ │ ├── Resource/ │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── applewatch.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── carplay.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── check.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── clear.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── convert.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── copy.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── drop.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── error.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── export.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── format.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── hyphen.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── ipad.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── ipaddress.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── iphone.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── mac.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── network.status.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── number.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── open.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── paramators.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── paste.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── pulldown.indicator.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── search.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── settings.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── sidebar.disclosure.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── spacing.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── speed.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── spuit.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── stepper.down.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── stepper.up.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── text.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── tool/ │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── api.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── audio.convert.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── base64.coder.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── color.blindness.simulator.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── color.picker.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── convert.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── date.convert.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── encoder.decoder.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── formatter.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── generator.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── gif.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── graphic.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── hash.generator.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── home.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── html.coder.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── icon.generator.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── image.compressor.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── image.converter.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── json.convert.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── json.formatter.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── json.search.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── jwt.coder.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── lorem.ipsum.generator.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── markdown.preview.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── media.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── network.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── number.base.convert.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── pdf.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── qr.generator.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── regex.tester.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── settings.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── sql.formatter.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── text.diff.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── text.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── text.inspector.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── url.coder.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── uuid.generator.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── xml.formatter.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── transparent_background.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj/ │ │ │ │ └── Main.storyboard │ │ │ ├── R.swift │ │ │ ├── de.lproj/ │ │ │ │ ├── Localizable.strings │ │ │ │ └── Main.strings │ │ │ ├── en.lproj/ │ │ │ │ ├── Localizable.strings │ │ │ │ └── Main.strings │ │ │ ├── es.lproj/ │ │ │ │ ├── Localizable.strings │ │ │ │ └── Main.strings │ │ │ ├── ja.lproj/ │ │ │ │ ├── Localizable.strings │ │ │ │ └── Main.strings │ │ │ ├── pt-BR.lproj/ │ │ │ │ ├── Localizable.strings │ │ │ │ └── Main.strings │ │ │ └── zh-Hans.lproj/ │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ └── Sidebar/ │ │ ├── SearchCell.swift │ │ ├── SidebarView+.swift │ │ ├── ToolCategoryCell.swift │ │ ├── ToolMenuCell.swift │ │ └── View/ │ │ └── Separator.swift │ └── DevToys.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata/ │ └── xcschemes/ │ └── DevToys.xcscheme ├── DevToys.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ ├── IDEWorkspaceChecks.plist │ └── swiftpm/ │ └── Package.resolved ├── LICENSE ├── README.md ├── Release Checklist.md └── docs/ ├── index.html └── sparkle/ └── appcast.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Mac .DS_Store # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## User settings xcuserdata/ ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint *.xccheckout ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) build/ DerivedData/ *.moved-aside *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 ## Obj-C/Swift specific *.hmap ## App packaging *.ipa *.dSYM.zip *.dSYM ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins # Package.resolved # *.xcodeproj # # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project # .swiftpm .build/ # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # # Pods/ # # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build/ # Accio dependency management Dependencies/ .accio/ # fastlane # # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output # Code Injection # # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ ================================================ FILE: CoreUtil/CoreUtil/Class/Action.swift ================================================ // // Action.swift // CoreUtil // // Created by yuki on 2022/01/16. // Copyright © 2022 yuki. All rights reserved. // public struct Action { public let title: String public let action: () -> () public init(title: String, action: @escaping () -> ()) { self.title = title self.action = action } } ================================================ FILE: CoreUtil/CoreUtil/Class/Delta.swift ================================================ // // Delta.swift // CoreUtil // // Created by yuki on 2020/05/14. // Copyright © 2020 yuki. All rights reserved. // public enum Delta { case to(Value) case by(Value) } extension Delta { @inlinable public func map(_ tranceform: (Value) throws -> T) rethrows -> Delta { switch self { case .to(let value): return .to(try tranceform(value)) case .by(let value): return .by(try tranceform(value)) } } @inlinable public func reduce(_ initialValue: Value, _ tranceform: (Value, Value) throws -> Value) rethrows -> Value { switch self { case .to(let value): return value case .by(let value): return try tranceform(initialValue, value) } } @inlinable public func takeValue() -> Value { switch self { case .to(let value): return value case .by(let value): return value } } @inlinable public func apply(_ initialValue: inout Value, _ tranceform: (Value, Value) throws -> Value) rethrows { initialValue = try reduce(initialValue, tranceform) } } extension Delta where Value: AdditiveArithmetic { @inlinable public func reduce(_ initialValue: Value) -> Value { reduce(initialValue, +) } @inlinable public func apply(_ initialValue: inout Value) { apply(&initialValue, +) } @inlinable public static func += (_ initialValue: inout Value, delta: Delta) { delta.apply(&initialValue) } } extension Delta where Value: RangeReplaceableCollection { @inlinable public func reduce(_ initialValue: Value) -> Value { reduce(initialValue, +) } @inlinable public func apply(_ initialValue: inout Value) { apply(&initialValue, +) } @inlinable public static func += (_ initialValue: inout Value, delta: Delta) { delta.apply(&initialValue) } } extension Delta: Encodable where Value: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { case .by(let value): try container.encode(["by": value]) case .to(let value): try container.encode(["to": value]) } } } extension Delta: Decodable where Value: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let content = try container.decode([String: Value].self) guard let (key, value) = content.first else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Empty container") } switch key { case "by": self = .by(value) case "to": self = .to(value) default: throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unkown key '\(key)'") } } } ================================================ FILE: CoreUtil/CoreUtil/Class/NS+OnAwake.swift ================================================ // // NSLoadView.swift // CoreUtil // // Created by yuki on 2020/08/31. // Copyright © 2020 yuki. All rights reserved. // import Cocoa open class NSLoadView: NSView { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class NSLoadTextView: NSTextView { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class NSLoadTableRowView: NSTableRowView { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class NSLoadVisualEffectView: NSVisualEffectView { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class NSLoadImageView: NSImageView { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class NSLoadStackView: NSStackView { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class NSLoadTextField: NSTextField { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class NSLoadTokenField: NSTokenField { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class NSLoadSearchField: NSSearchField { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class NSLoadScrollView: NSScrollView { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class NSLoadTableView: NSTableView { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class NSLoadSplitView: NSSplitView { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class NSLoadControl: NSControl { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class NSLoadButton: NSButton { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class NSLoadCollectionView: NSCollectionView { open func onAwake() {} public override init(frame frameRect: NSRect) { super.init(frame: frameRect) onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } } open class CALoadLayer: CALayer { open func onAwake() {} public override init() { super.init() onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } public override init(layer: Any) { super.init(layer: layer) onAwake() } } open class CALoadShapeLayer: CAShapeLayer { open func onAwake() {} public override init() { super.init() onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } public override init(layer: Any) { super.init(layer: layer) onAwake() } } open class CALoadTextLayer: CATextLayer { open func onAwake() {} public override init() { super.init() onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } public override init(layer: Any) { super.init(layer: layer) onAwake() } } open class CALoadGradientLayer: CAGradientLayer { open func onAwake() {} public override init() { super.init() onAwake() } public required init?(coder: NSCoder) { super.init(coder: coder) onAwake() } public override init(layer: Any) { super.init(layer: layer) onAwake() } } ================================================ FILE: CoreUtil/CoreUtil/Class/NSColorView.swift ================================================ // // NSColorView.swift // CoreUtil // // Created by yuki on 2021/10/05. // Copyright © 2021 yuki. All rights reserved. // import Cocoa open class NSColorView: NSLoadView { open var borderWidth: CGFloat = 1 { didSet { setNeedsDisplay(bounds) } } open var borderColor: NSColor? { didSet { setNeedsDisplay(bounds) } } open var backgroundColor: NSColor? { didSet { setNeedsDisplay(bounds) } } open override func draw(_ dirtyRect: NSRect) { if let backgroundColor = self.backgroundColor { backgroundColor.setFill() dirtyRect.fill() } if let borderColor = self.borderColor { borderColor.setStroke() let path = NSBezierPath(rect: dirtyRect) path.lineWidth = borderWidth path.stroke() } } } ================================================ FILE: CoreUtil/CoreUtil/Class/NSViewController+ChainObject.swift ================================================ // // NSViewController.swift // CoreUtil // // Created by yuki on 2021/07/19. // Copyright © 2021 yuki. All rights reserved. // import Cocoa private var chainObjectKey = 0 private var chainObjectFlagKey = 0 extension NSViewController { public var isChainObjectLoaded: Bool { get { objc_getAssociatedObject(self, &chainObjectFlagKey) as? Bool ?? false } set { objc_setAssociatedObject(self, &chainObjectFlagKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } public var chainObject: Any? { get { objc_getAssociatedObject(self, &chainObjectKey) } set { guard let chainObject = newValue else { return } if self.isChainObjectLoaded { return }; self.isChainObjectLoaded = true objc_setAssociatedObject(self, &chainObjectKey, chainObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) Self.activateObjectChain for child in children { child.chainObject = chainObject } self.chainObjectDidLoad() } } @objc open func chainObjectDidLoad() {} static let activateObjectChain: () = method_exchangeImplementations( class_getInstanceMethod(NSViewController.self, #selector(addChild))!, class_getInstanceMethod(NSViewController.self, #selector(chain_addChild))! ) @objc private dynamic func chain_addChild(_ childViewController: NSViewController) { childViewController.chainObject = chainObject self.chain_addChild(childViewController) } } ================================================ FILE: CoreUtil/CoreUtil/Class/NSViewController+StateObject.swift ================================================ // // NSViewController+StateObject.swift // CoreUtil // // Created by yuki on 2021/06/24. // Copyright © 2021 yuki. All rights reserved. // import Cocoa import Combine public struct StateChannel { let rawValue: String public init(_ rawValue: String) { self.rawValue = rawValue } } extension NSViewController { public func getState(for channel: StateChannel) -> Value? { getState(for: channel).flatMap{ $0 } } public func getState(for channel: StateChannel) -> Value? { publisher(for: channel.rawValue).value as? Value } public func setState(_ value: Value?, for channel: StateChannel) { Self.activateStateObject() _setState(value, for: channel.rawValue) } public func getStatePublisher(for channel: StateChannel) -> AnyPublisher { publisher(for: channel.rawValue).map{ $0 as? Value }.eraseToAnyPublisher() } public func getStatePublisher(for channel: StateChannel) -> AnyPublisher where Value: AnyObject { publisher(for: channel.rawValue).map{ $0 as? Value }.removeDuplicates(by: ===).eraseToAnyPublisher() } public func getStatePublisher(for channel: StateChannel) -> AnyPublisher where Value: AnyObject { publisher(for: channel.rawValue).map{ $0 as? Value }.removeDuplicates(by: ===).eraseToAnyPublisher() } public func linkState(for channel: StateChannel, to viewController: NSViewController) { getStatePublisher(for: channel).sink{ viewController.setState($0, for: channel) }.store(in: &self.objectBag) } } extension NSViewController { private static func activateStateObject() { enum __ { static let __: () = method_exchangeImplementations( class_getInstanceMethod(NSViewController.self, #selector(addChild))!, class_getInstanceMethod(NSViewController.self, #selector(_addChild))! ) } __.__ } private func _setState(_ value: Any?, for channel: String) { publisher(for: channel).send(value) for child in children { child._setState(value, for: channel) } } private static var envContainerKey = 0 private var envContainer: [String: CurrentValueSubject] { get { objc_getAssociatedObject(self, &Self.envContainerKey) as? [String: CurrentValueSubject] ?? [:] } set { objc_setAssociatedObject(self, &Self.envContainerKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } private func publisher(for channel: String) -> CurrentValueSubject { return envContainer[channel] ?? CurrentValueSubject(nil) => { envContainer[channel] = $0 } } @objc private dynamic func _addChild(_ childViewController: NSViewController) { for channel in envContainer.keys { guard let value = publisher(for: channel).value else { continue } childViewController._setState(value, for: channel) } self._addChild(childViewController) } } ================================================ FILE: CoreUtil/CoreUtil/Class/Observable.swift ================================================ // // Property.swift // CoreUtil // // Created by yuki on 2020/07/06. // Copyright © 2020 yuki. All rights reserved. // import Combine /// Publishedの10倍軽いPropertyWrapper @propertyWrapper public struct Observable { public struct Publisher: Combine.Publisher { public typealias Output = Value public typealias Failure = Never @usableFromInline let subject: CurrentValueSubject @inlinable init(_ value: Value) { self.subject = CurrentValueSubject(value) } @inlinable public func receive(subscriber: S) where S.Failure == Self.Failure, S.Input == Self.Output { self.subject.receive(subscriber: subscriber) } } public let projectedValue: Publisher @inlinable public var wrappedValue: Value { @inlinable get { projectedValue.subject.value } @inlinable set { projectedValue.subject.send(newValue) } } @inlinable public init(wrappedValue value: Value) { self.projectedValue = Publisher(value) } } ================================================ FILE: CoreUtil/CoreUtil/Class/PipeOperator.swift ================================================ // // +Functions.swift // CoreUtil // // Created by yuki on 2020/10/01. // Copyright © 2020 yuki. All rights reserved. // precedencegroup PipePrecedence { higherThan: NilCoalescingPrecedence associativity: left } infix operator =>: PipePrecedence infix operator |>: PipePrecedence infix operator <=>: PipePrecedence @discardableResult @inlinable public func => (lhs: T, rhs: (T) throws -> Void) rethrows -> T { try rhs(lhs) return lhs } @discardableResult @inlinable public func |> (lhs: T, rhs: (T) throws -> U) rethrows -> U { try rhs(lhs) } @discardableResult @inlinable public func <=> (lhs: T, rhs: (inout T) throws -> Void) rethrows -> T { var lhs = lhs try rhs(&lhs) return lhs } ================================================ FILE: CoreUtil/CoreUtil/Class/Query.swift ================================================ // // Query.swift // CoreUtil // // Created by yuki on 2020/01/13. // Copyright © 2020 yuki. All rights reserved. // /// 検索時のクエリを表す。検索時の処理を一般化することが目的 /// /// # 大まかな仕様 /// - 空クエリは全てにマッチ /// - 大文字小文字を考慮せずに検索を行う /// - 空白文字で区切った文字の全てにマッチするときにマッチする /// /// # サンプル /// ``` /// let query = Query("Hello World") /// /// query.matches(to: "") // true /// query.matches(to: "Hello whole new world.") // true /// query.matches(to: "Hello swift.") // false /// ``` public struct Query { /// 検索を行う要素 let components: [String] /// 文字列で初期化 public init(_ query: String) { self.components = query.components(separatedBy: .whitespaces) .map{ $0.lowercased() } .filter{ !$0.isEmpty } } /// 空クエリで初期化 public init() { self.components = [] } public var isEmpty: Bool { self.components.isEmpty } /// マッチ判定を行う。詳細は`Query`のDiscussionを参照。 public func matches(to contents: String...) -> Bool { if self.components.isEmpty { return true } let contents = contents.map{ $0.lowercased() } return components.allSatisfy{ component in contents.contains{ $0.contains(component) } } } } ================================================ FILE: CoreUtil/CoreUtil/Class/Reachability+Publisher.swift ================================================ // // Reachability+Ex.swift // CoreUtil // // Created by yuki on 2021/09/14. // Copyright © 2021 yuki. All rights reserved. // import Foundation import Combine extension Reachability { public struct Publisher: Combine.Publisher { public typealias Output = Reachability public typealias Failure = Never let subject: CurrentValueSubject init(_ reachability: Reachability) { self.subject = CurrentValueSubject(reachability) reachability.whenReachable = {[subject] in subject.send($0) } reachability.whenUnreachable = {[subject] in subject.send($0) } try? reachability.startNotifier() } public func receive(subscriber: S) where S.Failure == Self.Failure, S.Input == Self.Output { self.subject.receive(subscriber: subscriber) } } } ================================================ FILE: CoreUtil/CoreUtil/Class/Reachability.swift ================================================ /* Copyright (c) 2014, Ashley Mills All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import SystemConfiguration import Foundation public enum ReachabilityError: Error { case failedToCreateWithAddress(sockaddr, Int32) case failedToCreateWithHostname(String, Int32) case unableToSetCallback(Int32) case unableToSetDispatchQueue(Int32) case unableToGetFlags(Int32) } @available(*, unavailable, renamed: "Notification.Name.reachabilityChanged") public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification") public extension Notification.Name { static let reachabilityChanged = Notification.Name("reachabilityChanged") } public class Reachability { public typealias NetworkReachable = (Reachability) -> () public typealias NetworkUnreachable = (Reachability) -> () public private(set) lazy var publisher = Publisher(self) public enum Connection: CustomStringConvertible { case unavailable, wifi, cellular public var description: String { switch self { case .cellular: return "Cellular" case .wifi: return "WiFi" case .unavailable: return "No Connection" } } } public var whenReachable: NetworkReachable? public var whenUnreachable: NetworkUnreachable? /// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`) public var allowsCellularConnection: Bool // The notification center on which "reachability changed" events are being posted public var notificationCenter: NotificationCenter = NotificationCenter.default public var connection: Connection { if flags == nil { try? setReachabilityFlags() } switch flags?.connection { case .unavailable?, nil: return .unavailable case .cellular?: return allowsCellularConnection ? .cellular : .unavailable case .wifi?: return .wifi } } fileprivate var isRunningOnDevice: Bool = { #if targetEnvironment(simulator) return false #else return true #endif }() fileprivate(set) var notifierRunning = false fileprivate let reachabilityRef: SCNetworkReachability fileprivate let reachabilitySerialQueue: DispatchQueue fileprivate let notificationQueue: DispatchQueue? fileprivate(set) var flags: SCNetworkReachabilityFlags? { didSet { guard flags != oldValue else { return } notifyReachabilityChanged() } } required public init(reachabilityRef: SCNetworkReachability, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil, notificationQueue: DispatchQueue? = .main) { self.allowsCellularConnection = true self.reachabilityRef = reachabilityRef self.reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability", qos: queueQoS, target: targetQueue) self.notificationQueue = notificationQueue } public convenience init(hostname: String, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil, notificationQueue: DispatchQueue? = .main) throws { guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { throw ReachabilityError.failedToCreateWithHostname(hostname, SCError()) } self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue, notificationQueue: notificationQueue) } public convenience init(queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil, notificationQueue: DispatchQueue? = .main) throws { var zeroAddress = sockaddr() zeroAddress.sa_len = UInt8(MemoryLayout.size) zeroAddress.sa_family = sa_family_t(AF_INET) guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { throw ReachabilityError.failedToCreateWithAddress(zeroAddress, SCError()) } self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue, notificationQueue: notificationQueue) } deinit { stopNotifier() } } public extension Reachability { // MARK: - *** Notifier methods *** func startNotifier() throws { guard !notifierRunning else { return } let callback: SCNetworkReachabilityCallBack = { (reachability, flags, info) in guard let info = info else { return } // `weakifiedReachability` is guaranteed to exist by virtue of our // retain/release callbacks which we provided to the `SCNetworkReachabilityContext`. let weakifiedReachability = Unmanaged.fromOpaque(info).takeUnretainedValue() // The weak `reachability` _may_ no longer exist if the `Reachability` // object has since been deallocated but a callback was already in flight. weakifiedReachability.reachability?.flags = flags } let weakifiedReachability = ReachabilityWeakifier(reachability: self) let opaqueWeakifiedReachability = Unmanaged.passUnretained(weakifiedReachability).toOpaque() var context = SCNetworkReachabilityContext( version: 0, info: UnsafeMutableRawPointer(opaqueWeakifiedReachability), retain: { (info: UnsafeRawPointer) -> UnsafeRawPointer in let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) _ = unmanagedWeakifiedReachability.retain() return UnsafeRawPointer(unmanagedWeakifiedReachability.toOpaque()) }, release: { (info: UnsafeRawPointer) -> Void in let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) unmanagedWeakifiedReachability.release() }, copyDescription: { (info: UnsafeRawPointer) -> Unmanaged in let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) let weakifiedReachability = unmanagedWeakifiedReachability.takeUnretainedValue() let description = weakifiedReachability.reachability?.description ?? "nil" return Unmanaged.passRetained(description as CFString) } ) if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) { stopNotifier() throw ReachabilityError.unableToSetCallback(SCError()) } if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) { stopNotifier() throw ReachabilityError.unableToSetDispatchQueue(SCError()) } // Perform an initial check try setReachabilityFlags() notifierRunning = true } func stopNotifier() { defer { notifierRunning = false } SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil) SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil) } var description: String { return flags?.description ?? "unavailable flags" } } fileprivate extension Reachability { func setReachabilityFlags() throws { try reachabilitySerialQueue.sync { [unowned self] in var flags = SCNetworkReachabilityFlags() if !SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags) { self.stopNotifier() throw ReachabilityError.unableToGetFlags(SCError()) } self.flags = flags } } func notifyReachabilityChanged() { let notify = { [weak self] in guard let self = self else { return } self.connection != .unavailable ? self.whenReachable?(self) : self.whenUnreachable?(self) self.notificationCenter.post(name: .reachabilityChanged, object: self) } // notify on the configured `notificationQueue`, or the caller's (i.e. `reachabilitySerialQueue`) notificationQueue?.async(execute: notify) ?? notify() } } extension SCNetworkReachabilityFlags { typealias Connection = Reachability.Connection var connection: Connection { guard isReachableFlagSet else { return .unavailable } // If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi #if targetEnvironment(simulator) return .wifi #else var connection = Connection.unavailable if !isConnectionRequiredFlagSet { connection = .wifi } if isConnectionOnTrafficOrDemandFlagSet { if !isInterventionRequiredFlagSet { connection = .wifi } } if isOnWWANFlagSet { connection = .cellular } return connection #endif } var isOnWWANFlagSet: Bool { #if os(iOS) return contains(.isWWAN) #else return false #endif } var isReachableFlagSet: Bool { return contains(.reachable) } var isConnectionRequiredFlagSet: Bool { return contains(.connectionRequired) } var isInterventionRequiredFlagSet: Bool { return contains(.interventionRequired) } var isConnectionOnTrafficFlagSet: Bool { return contains(.connectionOnTraffic) } var isConnectionOnDemandFlagSet: Bool { return contains(.connectionOnDemand) } var isConnectionOnTrafficOrDemandFlagSet: Bool { return !intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty } var isTransientConnectionFlagSet: Bool { return contains(.transientConnection) } var isLocalAddressFlagSet: Bool { return contains(.isLocalAddress) } var isDirectFlagSet: Bool { return contains(.isDirect) } var isConnectionRequiredAndTransientFlagSet: Bool { return intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection] } var description: String { let W = isOnWWANFlagSet ? "W" : "-" let R = isReachableFlagSet ? "R" : "-" let c = isConnectionRequiredFlagSet ? "c" : "-" let t = isTransientConnectionFlagSet ? "t" : "-" let i = isInterventionRequiredFlagSet ? "i" : "-" let C = isConnectionOnTrafficFlagSet ? "C" : "-" let D = isConnectionOnDemandFlagSet ? "D" : "-" let l = isLocalAddressFlagSet ? "l" : "-" let d = isDirectFlagSet ? "d" : "-" return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)" } } /** `ReachabilityWeakifier` weakly wraps the `Reachability` class in order to break retain cycles when interacting with CoreFoundation. CoreFoundation callbacks expect a pair of retain/release whenever an opaque `info` parameter is provided. These callbacks exist to guard against memory management race conditions when invoking the callbacks. #### Race Condition If we passed `SCNetworkReachabilitySetCallback` a direct reference to our `Reachability` class without also providing corresponding retain/release callbacks, then a race condition can lead to crashes when: - `Reachability` is deallocated on thread X - A `SCNetworkReachability` callback(s) is already in flight on thread Y #### Retain Cycle If we pass `Reachability` to CoreFoundtion while also providing retain/ release callbacks, we would create a retain cycle once CoreFoundation retains our `Reachability` class. This fixes the crashes and his how CoreFoundation expects the API to be used, but doesn't play nicely with Swift/ARC. This cycle would only be broken after manually calling `stopNotifier()` — `deinit` would never be called. #### ReachabilityWeakifier By providing both retain/release callbacks and wrapping `Reachability` in a weak wrapper, we: - interact correctly with CoreFoundation, thereby avoiding a crash. See "Memory Management Programming Guide for Core Foundation". - don't alter the public API of `Reachability.swift` in any way - still allow for automatic stopping of the notifier on `deinit`. */ private class ReachabilityWeakifier { weak var reachability: Reachability? init(reachability: Reachability) { self.reachability = reachability } } ================================================ FILE: CoreUtil/CoreUtil/Class/RestorableData.swift ================================================ // // RestorableData.swift // CoreUtil // // Created by yuki on 2022/02/03. // import Cocoa extension FileManager { public static let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(Bundle.main.bundleIdentifier ?? "com.noname.app") } private let encoder = JSONEncoder() private let decoder = JSONDecoder() private let restorableDataURL = FileManager.temporaryDirectoryURL.appendingPathComponent("RestorableData") => { try? FileManager.default.createDirectory(at: $0, withIntermediateDirectories: true, attributes: nil) } @propertyWrapper public struct RestorableData { public struct Publisher: Combine.Publisher { public typealias Output = Value public typealias Failure = Never let subject: CurrentValueSubject init(_ value: Value) { self.subject = CurrentValueSubject(value) } public func receive(subscriber: S) where S.Failure == Self.Failure, S.Input == Self.Output { self.subject.receive(subscriber: subscriber) } } public let projectedValue: Publisher public let key: String public let fileURL: URL public var wrappedValue: Value { get { projectedValue.subject.value } set { projectedValue.subject.send(newValue) do { try encoder.encode(newValue).write(to: fileURL) } catch {} } } public init(wrappedValue initialValue: Value, _ key: String) { self.key = key self.fileURL = restorableDataURL.appendingPathComponent(key + ".json") let wrappedValue: Value do { wrappedValue = try decoder.decode(Value.self, from: Data(contentsOf: fileURL)) } catch { wrappedValue = initialValue } self.projectedValue = Publisher(wrappedValue) } } final public class NSImageContainer: Codable { static let dataDirectoryURL = FileManager.temporaryDirectoryURL.appendingPathComponent("NSImageContainer") => { try? FileManager.default.createDirectory(at: $0, withIntermediateDirectories: true, attributes: nil) } public let image: NSImage public let id: String public init(_ image: NSImage) { self.image = image self.id = UUID().uuidString } public static func wrap(_ image: NSImage) -> NSImageContainer { NSImageContainer(image) } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(id) let fileURL = NSImageContainer.dataDirectoryURL.appendingPathComponent(id) if !FileManager.default.fileExists(atPath: fileURL.path) { try image.tiffRepresentation?.write(to: fileURL) } } public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let id = try container.decode(String.self) let fileURL = NSImageContainer.dataDirectoryURL.appendingPathComponent(id) guard let image = NSImage(contentsOf: fileURL) else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "No image")) } self.image = image self.id = id } } ================================================ FILE: CoreUtil/CoreUtil/Class/RestorableState.swift ================================================ // // RestorableState.swift // CoreUtil // // Created by yuki on 2022/02/03. // import Foundation @propertyWrapper public struct RestorableState { public struct Publisher: Combine.Publisher { public typealias Output = Value public typealias Failure = Never let subject: CurrentValueSubject init(_ value: Value) { self.subject = CurrentValueSubject(value) } public func receive(subscriber: S) where S.Failure == Self.Failure, S.Input == Self.Output { self.subject.receive(subscriber: subscriber) } } public let projectedValue: Publisher public let key: String public var wrappedValue: Value { get { projectedValue.subject.value } set { projectedValue.subject.send(newValue) UserDefaults.standard.set(newValue.rawValue, forKey: key) } } public init(wrappedValue initialValue: Value, _ key: String) { let wrappedValue: Value if let rawValue = UserDefaults.standard.object(forKey: key) as? Value.RawValue, let value = Value(rawValue: rawValue) { wrappedValue = value } else { wrappedValue = initialValue } self.projectedValue = Publisher(wrappedValue) self.key = key } } extension String: RawRepresentable { public var rawValue: Self { self } public init(rawValue: Self) { self = rawValue } } extension Bool: RawRepresentable { public var rawValue: Self { self } public init(rawValue: Self) { self = rawValue } } extension Optional: RawRepresentable { public var rawValue: Self { self } public init(rawValue: Self) { self = rawValue } } extension Int: RawRepresentable { public var rawValue: Self { self } public init(rawValue: Self) { self = rawValue } } extension CGFloat: RawRepresentable { public var rawValue: Self { self } public init(rawValue: Self) { self = rawValue } } extension Double: RawRepresentable { public var rawValue: Self { self } public init(rawValue: Self) { self = rawValue } } ================================================ FILE: CoreUtil/CoreUtil/Class/Terminal.swift ================================================ // // Terminal.swift // CoreUtil // // Created by yuki on 2022/02/11. // import Foundation public enum TerminalError: Error, CustomStringConvertible { case nonZeroExit(String) public var description: String { switch self { case .nonZeroExit(let string): return string } } } public enum Terminal { public struct ExecuteOption: OptionSet { public let rawValue: UInt64 public init(rawValue: UInt64) { self.rawValue = rawValue } public static let standardOutput = ExecuteOption(rawValue: 1 << 0) public static let standardError = ExecuteOption(rawValue: 1 << 1) } public static func run(_ executableURL: URL, arguments: [String], queue: DispatchQueue = .global(), options: ExecuteOption = .all) -> Promise { let task = Process() let outputPipe = Pipe() let errorPipe = Pipe() task.executableURL = executableURL task.arguments = arguments if options.contains(.standardOutput) { task.standardOutput = outputPipe } if options.contains(.standardError) { task.standardError = errorPipe } return Promise.tryAsync(on: queue) { resolve, reject in try task.run() task.waitUntilExit() if task.terminationStatus != 0 { reject(TerminalError.nonZeroExit(errorPipe.readStringToEndOfFile ?? "[binary]")) } else { resolve(outputPipe.readStringToEndOfFile ?? "[binary]") } } } } extension Pipe { public var readStringToEndOfFile: String? { String(data: self.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) } } ================================================ FILE: CoreUtil/CoreUtil/Class/ViewPlaceholder.swift ================================================ // // ViewPlaceholder.swift // CoreUtil // // Created by yuki on 2021/06/03. // Copyright © 2021 yuki. All rights reserved. // import Cocoa import Combine open class NSPlaceholderView: NSLoadView { public var contentView: View? { didSet { oldValue?.removeFromSuperview() if let contentView = contentView { self.addSubview(contentView) contentView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ contentView.rightAnchor.constraint(equalTo: self.rightAnchor), contentView.leftAnchor.constraint(equalTo: self.leftAnchor), contentView.topAnchor.constraint(equalTo: self.topAnchor), contentView.bottomAnchor.constraint(equalTo: self.bottomAnchor), ]) } } } } ================================================ FILE: CoreUtil/CoreUtil/Class/Zip3Sequence.swift ================================================ // // Zip3Sequence.swift // CoreUtil // // Created by yuki on 2022/01/22. // Copyright © 2022 yuki. All rights reserved. // public func zip(_ a: A, _ b: B, _ c: C) -> Zip3Sequence { Zip3Sequence(a, b, c) } public func zip(_ a: A, _ b: B, _ c: C, _ d: D) -> Zip4Sequence { Zip4Sequence(a, b, c, d) } public struct Zip3Sequence: Sequence { public typealias Element = (A.Element, B.Element, C.Element) public let a: A public let b: B public let c: C public struct Iterator: IteratorProtocol { var a: A.Iterator var b: B.Iterator var c: C.Iterator mutating public func next() -> Element? { if let a = a.next(), let b = b.next(), let c = c.next() { return (a, b, c) }; return nil } } public func makeIterator() -> Iterator { Iterator(a: a.makeIterator(), b: b.makeIterator(), c: c.makeIterator()) } init(_ a: A, _ b: B, _ c: C) { self.a = a self.b = b self.c = c } } public struct Zip4Sequence: Sequence { public typealias Element = (A.Element, B.Element, C.Element, D.Element) public let a: A public let b: B public let c: C public let d: D public struct Iterator: IteratorProtocol { var a: A.Iterator var b: B.Iterator var c: C.Iterator var d: D.Iterator mutating public func next() -> Element? { if let a = a.next(), let b = b.next(), let c = c.next(), let d = d.next() { return (a, b, c, d) }; return nil } } public func makeIterator() -> Iterator { Iterator(a: a.makeIterator(), b: b.makeIterator(), c: c.makeIterator(), d: d.makeIterator()) } init(_ a: A, _ b: B, _ c: C, _ d: D) { self.a = a self.b = b self.c = c self.d = d } } ================================================ FILE: CoreUtil/CoreUtil/CoreUtil.h ================================================ // // CoreUtil.h // CoreUtil // // Created by yuki on 2022/01/29. // #import #import "ExceptionHanlder.h" //! Project version number for CoreUtil. FOUNDATION_EXPORT double CoreUtilVersionNumber; //! Project version string for CoreUtil. FOUNDATION_EXPORT const unsigned char CoreUtilVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import ================================================ FILE: CoreUtil/CoreUtil/ExceptionHanlder/ExceptionHandler.swift ================================================ // // ExceptionHandler.swift // CoreUtil // // Created by yuki on 2021/06/26. // Copyright © 2021 yuki. All rights reserved. // @inlinable public func objc_try(_ tryBlock: () -> ()) throws { var error: NSError? _ExceptionHandler.objc_try(tryBlock, objc_catch: { error = NSError(domain: "NSException", code: 999, userInfo: $0.userInfo?.reduce(into: [:]) { $0["\($1.key)"] = $1.value }) }, objc_finally: {}) if let error = error { throw error } } @inlinable public func objc_try(_ tryBlock: () -> T) throws -> T { var value: T? var error: NSError? _ExceptionHandler.objc_try({ value = tryBlock() }, objc_catch: { error = NSError(domain: "NSException", code: 999, userInfo: $0.userInfo?.reduce(into: [:]) { $0["\($1.key)"] = $1.value }) }, objc_finally: {}) if let error = error { throw error } return value! } @inlinable public func objc_try(_ tryBlock: () -> (), catch catchBlock: (NSException) -> (), finally finallyBlock: () -> () = {}) { _ExceptionHandler.objc_try(tryBlock, objc_catch: catchBlock, objc_finally: finallyBlock) } @inlinable public func objc_try(_ tryBlock: () -> T, catch catchBlock: (NSException) -> T, finally finallyBlock: () -> () = {}) -> T { var value: T! _ExceptionHandler.objc_try({ value = tryBlock() }, objc_catch: { value = catchBlock($0) }, objc_finally: finallyBlock) return value } ================================================ FILE: CoreUtil/CoreUtil/ExceptionHanlder/ExceptionHanlder.h ================================================ // // ExceptionHanlder.h // CoreUtil // // Created by yuki on 2021/06/26. // Copyright © 2021 yuki. All rights reserved. // #import @interface _ExceptionHandler : NSObject + (void)objc_try: (nonnull __attribute__((noescape)) void(^)(void))objc_try objc_catch: (nonnull __attribute__((noescape)) void(^)(NSException* _Nonnull))objc_catch objc_finally: (nullable __attribute__((noescape)) void(^)(void))objc_finally; @end ================================================ FILE: CoreUtil/CoreUtil/ExceptionHanlder/ExceptionHanlder.m ================================================ // // ExceptionHanlder.m // CoreUtil // // Created by yuki on 2021/06/26. // Copyright © 2021 yuki. All rights reserved. // #import #import @implementation _ExceptionHandler : NSObject + (void)objc_try: (nonnull __attribute__((noescape)) void(^)(void)) objc_try objc_catch: (nonnull __attribute__((noescape)) void(^)(NSException* _Nonnull)) objc_catch objc_finally: (nullable __attribute__((noescape)) void(^)(void)) objc_finally { @try { objc_try(); } @catch (NSException* exception) { objc_catch(exception); } @finally { if (objc_finally) { objc_finally(); } } } @end ================================================ FILE: CoreUtil/CoreUtil/Export.swift ================================================ // // Export.swift // CoreUtil // // Created by yuki on 2022/01/29. // @_exported import Promise @_exported import SnapKit @_exported import Cocoa @_exported import Combine ================================================ FILE: CoreUtil/CoreUtil/Extensions/Combine/Combine+ObjectBag.swift ================================================ // // NSObject+Combine.swift // CoreUtil // // Created by yuki on 2020/05/22. // Copyright © 2020 yuki. All rights reserved. // import Foundation import Combine private var bagKey: UInt8 = 0 extension NSObject { public var objectBag: Set { get { bagContainer.value } set { bagContainer.value = newValue } } private class BagContainer { var value = Set() } private var bagContainer: BagContainer { if let container = objc_getAssociatedObject(self, &bagKey) as? BagContainer { return container } let container = BagContainer() objc_setAssociatedObject(self, &bagKey, container, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return container } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/Combine/Combine+Peek.swift ================================================ // // Combine+Peek.swift // CoreUtil // // Created by yuki on 2022/02/01. // import Combine extension Publisher { public func peekError(_ block: @escaping (Failure) -> Void) -> Publishers.MapError { self.mapError { f -> Failure in block(f); return f } } public func peek(_ block: @escaping (Output) -> Void) -> Publishers.Map { self.map { block($0); return $0 } } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/Combine/NSControl+Combine.swift ================================================ // // NSButton+Combine.swift // CoreUtil // // Created by yuki on 2021/07/23. // Copyright © 2021 yuki. All rights reserved. // import Cocoa import Combine private var actionPublisherKey = 0 extension NSControl { final public class Publisher: Combine.Publisher { public typealias Output = Void public typealias Failure = Never final class Target: NSObject { let subject = PassthroughSubject() @objc func action(_ sender: NSControl) { self.subject.send() } } let target: Target init(control: NSControl) { let target = Target() control.target = target control.action = #selector(Target.action) self.target = target } public func receive(subscriber: S) where S.Failure == Failure, S.Input == Output { self.target.subject.receive(subscriber: subscriber) } } // actionとtargetを設定してpublisherとして出力できるようにします。 public var actionPublisher: Publisher { if let publisher = objc_getAssociatedObject(self, &actionPublisherKey) as? Publisher { return publisher } let publisher = Publisher(control: self) objc_setAssociatedObject(self, &actionPublisherKey, publisher, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return publisher } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/Combine/NSTextField+Combine.swift ================================================ // // Combine+TextField.swift // CoreUtil // // Created by yuki on 2021/01/03. // Copyright © 2021 yuki. All rights reserved. // import Cocoa import Combine private var delegateKey = 0 extension NSTextField { public var endEditingStringPublisher: AnyPublisher { pubilsherDelegate.endEditingPublisher.map{ $0.stringValue }.eraseToAnyPublisher() } public var changeStringPublisher: AnyPublisher { pubilsherDelegate.changePublisher.map{ $0.stringValue }.eraseToAnyPublisher() } public var beginEditingStringPublisher: AnyPublisher { pubilsherDelegate.beginEditingPublisher.map{ $0.stringValue }.eraseToAnyPublisher() } public var endEditingPublisher: AnyPublisher { pubilsherDelegate.endEditingPublisher.eraseToAnyPublisher() } public var changePublisher: AnyPublisher { pubilsherDelegate.changePublisher.eraseToAnyPublisher() } public var beginEditingPublisher: AnyPublisher { pubilsherDelegate.beginEditingPublisher.eraseToAnyPublisher() } final private class PublisherDelegate: NSObject, NSTextFieldDelegate { let endEditingPublisher = PassthroughSubject() let changePublisher = PassthroughSubject() let beginEditingPublisher = PassthroughSubject() func controlTextDidBeginEditing(_ obj: Notification) { guard let textField = obj.object as? NSTextField else { return } beginEditingPublisher.send(textField) } func controlTextDidChange(_ obj: Notification) { guard let textField = obj.object as? NSTextField else { return } changePublisher.send(textField) } func controlTextDidEndEditing(_ obj: Notification) { guard let textField = obj.object as? NSTextField else { return } endEditingPublisher.send(textField) } } private var pubilsherDelegate: PublisherDelegate { if let delegate = objc_getAssociatedObject(self, &delegateKey) as? PublisherDelegate { return delegate } let delegate = PublisherDelegate() objc_setAssociatedObject(self, &delegateKey, delegate, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) assert(self.delegate == nil, "Publisher delegate is not averable. \(delegate)") self.delegate = delegate return delegate } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/CoreGraphics/Ex+CGPoint.swift ================================================ // // Ex+CGPoint.swift // Topica // // Created by yuki on 2019/10/27. // Copyright © 2019 yuki. All rights reserved. // import CoreGraphics @inlinable public func round(_ point: CGPoint) -> CGPoint { CGPoint(x: round(point.x), y: round(point.y)) } @inlinable public func ceil(_ point: CGPoint) -> CGPoint { CGPoint(x: ceil(point.x), y: ceil(point.y)) } @inlinable public func floor(_ point: CGPoint) -> CGPoint { CGPoint(x: floor(point.x), y: floor(point.y)) } @inlinable public func sign(_ point: CGPoint) -> CGPoint { CGPoint(x: sign(point.x), y: sign(point.y)) } @inlinable public func min(_ point0: CGPoint, _ point1: CGPoint) -> CGPoint { CGPoint(x: min(point0.x, point1.x), y: min(point0.y, point1.y)) } @inlinable public func max(_ point0: CGPoint, _ point1: CGPoint) -> CGPoint { CGPoint(x: max(point0.x, point1.x), y: max(point0.y, point1.y)) } @inlinable public func abs(_ point: CGPoint) -> CGFloat { sqrt(point.x * point.x + point.y * point.y) } @inlinable public func abs2(_ point: CGPoint) -> CGFloat { point.x * point.x + point.y * point.y } @inlinable public func clamp(_ point: CGPoint, in rect: CGRect) -> CGPoint { CGPoint(x: clamp(point.x, into: rect.minX...rect.maxX), y: clamp(point.y, into: rect.minY...rect.maxY)) } @inlinable public func dot(_ point0: CGPoint, _ point1: CGPoint) -> CGFloat { point0.x * point1.x + point0.y * point1.y } extension CGPoint { public static let infinity = CGSize(width: CGFloat.infinity, height: .infinity) @inlinable public func convertToSize() -> CGSize { CGSize(width: x, height: y) } @inlinable public var unitVector: CGPoint { let absValue = abs(self) if absValue == 0 { return .zero } return self / absValue } @inlinable public var isFinite: Bool { x.isFinite && y.isFinite } } extension CGPoint { @inlinable public func map(_ tranceform: (CGFloat) -> (CGFloat)) -> CGPoint { mapX(tranceform).mapY(tranceform) } @inlinable public func mapX(_ tranceform: (CGFloat) -> (CGFloat)) -> CGPoint { CGPoint(x: tranceform(x), y: y) } @inlinable public func mapY(_ tranceform: (CGFloat) -> (CGFloat)) -> CGPoint { CGPoint(x: x, y: tranceform(y)) } } extension CGPoint: ExpressibleByArrayLiteral { @inlinable public init(arrayLiteral elements: CGFloat...) { assert(elements.count == 2) self.init(x: elements[0], y: elements[1]) } } extension CGPoint: Hashable { @inlinable public func hash(into hasher: inout Hasher) { hasher.combine(x) hasher.combine(y) } } extension CGPoint: AdditiveArithmetic { @inlinable public static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint { CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) } @inlinable public static func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint { CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y) } @inlinable public static prefix func - (lhs: CGPoint) -> CGPoint { CGPoint(x: -lhs.x, y: -lhs.y) } @inlinable public static func += (lhs: inout CGPoint, rhs: CGPoint) { lhs = CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) } @inlinable public static func -= (lhs: inout CGPoint, rhs: CGPoint) { lhs = CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y) } } extension CGPoint { @inlinable public static func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint { return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs) } @inlinable public static func * (lhs: CGPoint, rhs: CGPoint) -> CGPoint { return CGPoint(x: lhs.x * rhs.x, y: lhs.y * rhs.y) } @inlinable public static func / (lhs: CGPoint, rhs: CGFloat) -> CGPoint { return CGPoint(x: lhs.x / rhs, y: lhs.y / rhs) } @inlinable public static func / (lhs: CGPoint, rhs: CGPoint) -> CGPoint { return CGPoint(x: lhs.x / rhs.x, y: lhs.y / rhs.y) } @inlinable public static func *= (lhs: inout CGPoint, rhs: CGFloat) { lhs = CGPoint(x: lhs.x * rhs, y: lhs.y * rhs) } @inlinable public static func /= (lhs: inout CGPoint, rhs: CGFloat) { lhs = CGPoint(x: lhs.x / rhs, y: lhs.y / rhs) } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/CoreGraphics/Ex+CGRect.swift ================================================ // // Ex+CGRect.swift // CoreUtil // // Created by yuki on 2020/02/03. // Copyright © 2020 yuki. All rights reserved. // import Foundation @inlinable public func round(_ rect: CGRect) -> CGRect { CGRect(origin: round(rect.origin), end: round(rect.end)) } extension CGRect { @inlinable public var center: CGPoint { @inlinable get { CGPoint(x: midX, y: midY) } @inlinable set { self.origin = CGPoint(x: newValue.x - width/2, y: newValue.y - height/2) } } @inlinable public var end: CGPoint { @inlinable get { CGPoint(x: maxX, y: maxY) } @inlinable set { self.origin = CGPoint(x: newValue.x - width, y: newValue.y - height) } } @inlinable public func fattened(by inset: CGFloat) -> CGRect { CGRect(origin: self.origin - [inset, inset], size: self.size + [inset, inset]*2) } @inlinable public func slimmed(by inset: CGFloat) -> CGRect { CGRect(origin: self.origin + [inset, inset], size: self.size - [inset, inset]*2) } @inlinable public func fattened(by edgeInsets: NSEdgeInsets) -> CGRect { CGRect(origin: self.origin - [edgeInsets.left, edgeInsets.bottom], size: self.size + [edgeInsets.left + edgeInsets.right, edgeInsets.top + edgeInsets.bottom]) } @inlinable public func slimmed(by edgeInsets: NSEdgeInsets) -> CGRect { CGRect(origin: self.origin + [edgeInsets.left, edgeInsets.bottom], size: self.size - [edgeInsets.left + edgeInsets.right, edgeInsets.top + edgeInsets.bottom]) } } extension CGRect { @inlinable public func mapOrigin(_ tranceform: (CGPoint) -> (CGPoint)) -> CGRect { CGRect(origin: tranceform(origin), size: size) } @inlinable public func mapCenter(_ tranceform: (CGPoint) -> (CGPoint)) -> CGRect { CGRect(center: tranceform(center), size: size) } @inlinable public func mapEnd(_ tranceform: (CGPoint) -> (CGPoint)) -> CGRect { CGRect(end: tranceform(end), size: size) } @inlinable public func mapSizePreservingOrigin(_ tranceform: (CGSize) -> (CGSize)) -> CGRect { CGRect(origin: origin, size: tranceform(size)) } @inlinable public func mapSizePreservingCenter(_ tranceform: (CGSize) -> (CGSize)) -> CGRect { CGRect(center: center, size: tranceform(size)) } @inlinable public func mapSizePreservingEnd(_ tranceform: (CGSize) -> (CGSize)) -> CGRect { CGRect(end: end, size: tranceform(size)) } } extension CGRect { @inlinable public init(origin: CGPoint) { self.init(origin: origin, size: .zero) } @inlinable public init(size: CGSize) { self.init(origin: .zero, size: size) } @inlinable public init(origin: CGPoint, end: CGPoint) { self.init(origin: origin, size: (end - origin).convertToSize()) } @inlinable public init(center: CGPoint, size: CGSize) { self.init(origin: center - size.convertToPoint() / 2, size: size) } @inlinable public init(end: CGPoint, size: CGSize) { self.init(origin: end - size.convertToPoint(), size: size) } @inlinable public init(originX: CGFloat, centerY: CGFloat, size: CGSize) { self.init(origin: [originX, centerY - size.height/2], size: size) } @inlinable public init(centerX: CGFloat, originY: CGFloat, size: CGSize) { self.init(origin: [centerX - size.width/2, originY], size: size) } @inlinable public var isFinite: Bool { size.isFinite && origin.isFinite } } extension CGRect { @inlinable public static func + (lhs: CGRect, rhs: CGRect) -> CGRect { CGRect(origin: lhs.origin + rhs.origin, size: lhs.size + rhs.size) } @inlinable public static func - (lhs: CGRect, rhs: CGRect) -> CGRect { CGRect(origin: lhs.origin - rhs.origin, size: lhs.size - rhs.size) } } extension CGRect: Hashable { @inlinable public func hash(into hasher: inout Hasher) { hasher.combine(size) hasher.combine(origin) } } extension Collection where Element == CGRect { @inlinable public var enclosingRect: CGRect { if self.isEmpty { return .zero } var minX: CGFloat = .infinity var minY: CGFloat = .infinity var maxX: CGFloat = -.infinity var maxY: CGFloat = -.infinity for rect in self { if minX > rect.origin.x { minX = rect.origin.x } if minY > rect.origin.y { minY = rect.origin.y } let _maxX = rect.origin.x + rect.size.width if maxX < _maxX { maxX = _maxX } let _maxY = rect.origin.y + rect.size.height if maxY < _maxY { maxY = _maxY } } let rect = CGRect(origin: [minX, minY], end: [maxX, maxY]) return rect } } extension FloatingPoint { @inlinable public func isFiniteOrZero() -> Self { isFinite ? self : .zero } } extension CGPoint { @inlinable public func isFiniteOrZero() -> CGPoint { isFinite ? self : .zero } } extension CGSize { @inlinable public func isFiniteOrZero() -> CGSize { isFinite ? self : .zero } } extension CGRect { @inlinable public func isFiniteOrZero() -> CGRect { isFinite ? self : .zero } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/CoreGraphics/Ex+CGSize.swift ================================================ import CoreGraphics @inlinable public func round(_ size: CGSize) -> CGSize { CGSize(width: round(size.width), height: round(size.height)) } @inlinable public func abs(_ size: CGSize) -> CGSize { CGSize(width: abs(size.width), height: abs(size.height)) } @inlinable public func sign(_ size: CGSize) -> CGSize { CGSize(width: sign(size.width), height: sign(size.height)) } @inlinable public func min(_ size0: CGSize, _ size1: CGSize) -> CGSize { CGSize(width: min(size0.width, size1.width), height: min(size0.height, size1.height)) } @inlinable public func max(_ size0: CGSize, _ size1: CGSize) -> CGSize { CGSize(width: max(size0.width, size1.width), height: max(size0.height, size1.height)) } extension CGSize { @inlinable public func convertToPoint() -> CGPoint { CGPoint(x: width, y: height) } @inlinable public var minElement: CGFloat { min(abs(self.width), abs(self.height)) } @inlinable public var maxElement: CGFloat { max(abs(self.width), abs(self.height)) } @inlinable public var isFinite: Bool { width.isFinite && height.isFinite } public static let infinity = CGSize(width: CGFloat.infinity, height: .infinity) } extension CGSize { @inlinable public func mapWidth(_ tranceform: (CGFloat) -> (CGFloat)) -> CGSize { CGSize(width: tranceform(width), height: height) } @inlinable public func mapHeight(_ tranceform: (CGFloat) -> (CGFloat)) -> CGSize { CGSize(width: width, height: tranceform(height)) } } extension CGSize: ExpressibleByArrayLiteral { public init(arrayLiteral elements: CGFloat...) { assert(elements.count == 2) self.init(width: elements[0], height: elements[1]) } } extension CGSize: AdditiveArithmetic { @inlinable public static func + (lhs: CGSize, rhs: CGSize) -> CGSize { CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height) } @inlinable public static func - (lhs: CGSize, rhs: CGSize) -> CGSize { CGSize(width: lhs.width - rhs.width, height: lhs.height - rhs.height) } @inlinable public static prefix func - (lhs: CGSize) -> CGSize { CGSize(width: -lhs.width, height: -lhs.height) } } extension CGSize { @inlinable public static func * (lhs: CGSize, rhs: CGFloat) -> CGSize { return CGSize(width: lhs.width * rhs, height: lhs.height * rhs) } @inlinable public static func * (lhs: CGSize, rhs: CGSize) -> CGSize { return CGSize(width: lhs.width * rhs.width, height: lhs.height * rhs.height) } @inlinable public static func / (lhs: CGSize, rhs: CGFloat) -> CGSize { return CGSize(width: lhs.width / rhs, height: lhs.height / rhs) } @inlinable public static func / (lhs: CGSize, rhs: CGSize) -> CGSize { return CGSize(width: lhs.width / rhs.width, height: lhs.height / rhs.height) } @inlinable public static func *= (lhs: inout CGSize, rhs: CGFloat) { lhs.width *= rhs lhs.height *= rhs } @inlinable public static func /= (lhs: inout CGSize, rhs: CGFloat) { lhs.width /= rhs lhs.height /= rhs } } extension CGSize: Hashable { @inlinable public func hash(into hasher: inout Hasher) { hasher.combine(width) hasher.combine(height) } } extension CGSize { /// boundingBoxにアスペクトフィットするのに必要な変換レートを返します。 @inlinable public func aspectFitRatio(fitInside boundingBox: CGSize) -> CGFloat { let mW = boundingBox.width / self.width let mH = boundingBox.height / self.height if mH > mW { return boundingBox.width / self.width } else { return boundingBox.height / self.height } } /// boundingBoxにアスペクトフィルするのに必要な変換レートを返します。 @inlinable public func aspectFillRatio(fillInside boundingBox: CGSize) -> CGFloat { let mW = boundingBox.width / self.width let mH = boundingBox.height / self.height if mH < mW { return boundingBox.width / self.width } else { return boundingBox.height / self.height } } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/Swift/Ex+Array.swift ================================================ // // Extensions.swift // DANMAKER // // Created by yuki on 2015/06/24. // Copyright © 2015 yuki. All rights reserved. // extension RandomAccessCollection { @inlinable public func at(_ index: Self.Index) -> Element? { self.indices.contains(index) ? self[index] : nil } } extension Sequence { @inlinable public func count(where condition: (Element) throws -> Bool) rethrows -> Int { try self.lazy.filter(condition).count } @inlinable public func count(while condition: (Element) throws -> Bool) rethrows -> Int { var count = 0 for element in self { if try !condition(element) { return count } count += 1 } return count } @inlinable public func firstSome(where condition: (Element) throws -> T?) rethrows -> T? { try self.lazy.compactMap(condition).first } @inlinable public func allSome(_ tranceform: (Element) throws -> T?) rethrows -> [T]? { var values = [T]() for element in self { guard let element = try tranceform(element) else { return nil } values.append(element) } return values } } extension Array { public mutating func move(fromIndex: Int, toIndex: Int) { assert(indices.contains(fromIndex), "fromIndex '\(fromIndex)' is out of bounds.") if fromIndex == toIndex { return } let removed = self.remove(at: fromIndex) if fromIndex < toIndex { self.insert(removed, at: toIndex - 1) } else { self.insert(removed, at: toIndex) } } public mutating func move(fromRange: Range, toIndex: Int) where Range.Bound == Int { assert(indices.contains(toIndex), "toIndex '\(toIndex)' is out of bounds.") let range = fromRange.relative(to: self) if range.contains(toIndex) { return } let removed = self[range] self.removeSubrange(range) if range.upperBound < toIndex + range.count - 1 { self.insert(contentsOf: removed, at: toIndex - range.count + 1) } else { self.insert(contentsOf: removed, at: toIndex) } } } extension Array { @discardableResult @inlinable public mutating func removeFirst(where condition: (Element) throws -> Bool) rethrows -> Element? { for i in 0.. Element? { for index in 0.. Element { self.max() ?? replace } @inlinable public func min(_ replace: Element) -> Element { self.min() ?? replace } } extension Dictionary { public mutating func arrayAppend(_ value: T, forKey key: Key) where Self.Value == Array { if self[key] == nil { self[key] = [] } self[key]!.append(value) } public mutating func arrayAppend(contentsOf newElements: S, forKey key: Key) where Self.Value == Array, S.Element == T { if self[key] == nil { self[key] = [] } self[key]!.append(contentsOf: newElements) } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/Swift/Ex+Clamp.swift ================================================ // // Ex+Comparable.swift // CoreUtil // // Created by yuki on 2020/04/01. // Copyright © 2020 yuki. All rights reserved. // public func clamp(_ x: Target, into range: ClosedRange) -> Target { max(range.lowerBound, min(x, range.upperBound)) } public func clamp(_ x: Target, into range: PartialRangeFrom) -> Target { max(range.lowerBound, x) } public func clamp(_ x: Target, into range: PartialRangeThrough) -> Target { min(range.upperBound, x) } extension Strideable { public func clamped(_ range: Range) -> Self { max(range.lowerBound, min(self, range.upperBound.advanced(by: -1))) } } extension Comparable { public func clamped(_ range: ClosedRange) -> Self { clamp(self, into: range) } public func clamped(_ range: PartialRangeFrom) -> Self { clamp(self, into: range) } public func clamped(_ range: PartialRangeThrough) -> Self { clamp(self, into: range) } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/Swift/Ex+Localize.swift ================================================ // // Ex+Localize.swift // CoreUtil // // Created by yuki on 2022/02/13. // import Foundation extension String { public func localized() -> String { NSLocalizedString(self, comment: self) } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/Swift/Ex+Number.swift ================================================ // // Ex+NUmber.swift // CoreUtil // // Created by yuki on 2020/06/24. // Copyright © 2020 yuki. All rights reserved. // /// Returns `1` when the value greater than or equals to `0`. And returns `-1` when the value smaller than `0` @inlinable public func sign(_ value: T) -> T { if value >= T.zero { return 1 } else { return -1 } } extension Double { @inlinable public func rounded(toDecimal fractionDigits: Int) -> Self { let multiplier = pow(10, Self(fractionDigits)) return Darwin.round(self * multiplier) / multiplier } } extension Float { @inlinable public func rounded(toDecimal fractionDigits: Int) -> Self { let multiplier = pow(10, Self(fractionDigits)) return Darwin.round(self * multiplier) / multiplier } } extension CGFloat { @inlinable public func rounded(toDecimal fractionDigits: Int) -> Self { let multiplier = pow(10, Self(fractionDigits)) return Darwin.round(self * multiplier) / multiplier } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/Swift/Ex+OptionSet.swift ================================================ // // Ex+OptionSet.swift // CoreUtil // // Created by yuki on 2020/04/13. // Copyright © 2020 yuki. All rights reserved. // extension OptionSet where RawValue: FixedWidthInteger { public static var all: Self { Self(rawValue: RawValue.max) } public static var none: Self { [] } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/UI/Ex+CALayer.swift ================================================ // // Ex+CALayer.swift // CoreUtil // // Created by yuki on 2020/06/23. // Copyright © 2020 yuki. All rights reserved. // import Cocoa extension CALayer { public static func animationDisabled() -> Self { let layer = Self.init() layer.areAnimationsEnabled = false return layer } } extension CALayer { @objc public var areAnimationsEnabled: Bool { get { delegate === CALayerAnimationsDisablingDelegate.shared } set { delegate = newValue ? nil : CALayerAnimationsDisablingDelegate.shared } } } private class CALayerAnimationsDisablingDelegate: NSObject, CALayerDelegate { public static let shared = CALayerAnimationsDisablingDelegate() let null = NSNull() func action(for layer: CALayer, forKey event: String) -> CAAction? { null } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/UI/Ex+Image.swift ================================================ // // Ex+Image.swift // CoreUtil // // Created by yuki on 2020/03/10. // Copyright © 2020 yuki. All rights reserved. // import Cocoa extension NSImage { public var cgImage: CGImage? { var imageRect = CGRect(size: self.size) return cgImage(forProposedRect: &imageRect, context: nil, hints: nil) } } extension CGImage { public func convertToGrayscale() -> CGImage { let imageRect = CGRect(size: CGSize(width: width, height: height)) let context = CGContext( data: nil, width: self.width, height: self.height, bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue )! context.draw(self, in: imageRect) return context.makeImage()! } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/UI/Ex+NSColor.swift ================================================ import Cocoa // MARK: - Extension for Color extension NSColor { public convenience init(colorSpace: NSColorSpace = .current, hex: Int, alpha: CGFloat = 1.0) { let red = CGFloat((hex & 0xff_00_00) >> 16) / 255 let green = CGFloat((hex & 0x00_ff_00) >> 8) / 255 let blue = CGFloat((hex & 0x00_00_ff) >> 0) / 255 self.init(colorSpace: colorSpace, red: red, green: green, blue: blue, alpha: alpha) } public convenience init(colorSpace: NSColorSpace = .current, red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { var rgba = [red, green, blue, alpha] self.init(colorSpace: colorSpace, components: &rgba, count: rgba.count) } } extension NSColorSpace { public static let current: NSColorSpace = NSScreen.main?.colorSpace ?? NSColorSpace.deviceRGB } extension CGColorSpace { public static let current: CGColorSpace = NSColorSpace.current.cgColorSpace ?? CGColorSpaceCreateDeviceRGB() } ================================================ FILE: CoreUtil/CoreUtil/Extensions/UI/Ex+NSControl.swift ================================================ // // Ex+NSControl.swift // CoreUtil // // Created by yuki on 2021/10/28. // Copyright © 2021 yuki. All rights reserved. // import Cocoa extension NSControl { public func setTarget(_ target: AnyObject, action: Selector) { self.target = target self.action = action } public func executeAction() { guard let object = (target as? NSObject), let action = self.action else { return NSSound.beep() } if object.responds(to: action) { object.perform(action, with: self) } } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/UI/Ex+NSEdgeInsets.swift ================================================ // // Ex+NSEdgeInsets.swift // CoreUtil // // Created by yuki on 2022/01/29. // import Cocoa extension NSEdgeInsets: Equatable { public static let zero = NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) public static func == (lhs: NSEdgeInsets, rhs: NSEdgeInsets) -> Bool { lhs.top == rhs.top && lhs.left == rhs.left && lhs.bottom == rhs.bottom && lhs.right == rhs.right } public static func each(top: CGFloat = 0, left: CGFloat = 0, bottom: CGFloat = 0, right: CGFloat = 0) -> NSEdgeInsets { NSEdgeInsets(top: top, left: left, bottom: bottom, right: right) } public init(repeating element: CGFloat) { self.init(top: element, left: element, bottom: element, right: element) } public init(x: CGFloat=0, y: CGFloat=0) { self.init(top: y, left: x, bottom: y, right: x) } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/UI/Ex+NSEvent.swift ================================================ // // NSEvent+Location.swift // CoreUtil // // Created by yuki on 2021/11/11. // Copyright © 2021 yuki. All rights reserved. // import Cocoa extension NSEvent { @inlinable public func location(in view: NSView) -> CGPoint { view.convert(self.locationInWindow, from: nil) } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/UI/Ex+NSMenuItem.swift ================================================ // // Ex+NSMenuItem.swift // CoreUtil // // Created by yuki on 2020/08/20. // Copyright © 2020 yuki. All rights reserved. // import Cocoa import Combine private var actionHandlerKey = 0 private var actionPublisherKey = 0 extension NSMenuItem { public convenience init(title: String, image: NSImage? = nil, isSelected: Bool = false, isEnabled: Bool = true, action: (() -> Void)? = nil) { self.init() self.title = title self.image = image self.isSelected = isSelected if let action = action { self.setAction(action) } } public var isSelected: Bool { get { self.state == .on } set { self.state = newValue ? .on : .off } } public func setAction(_ block: @escaping () -> ()) { self.actionHandler.action = block } private var actionHandler: ActionHandler { if let handler = objc_getAssociatedObject(self, &actionHandlerKey) as? ActionHandler { return handler } let handler = ActionHandler() self.target = handler self.action = #selector(ActionHandler.run) objc_setAssociatedObject(self, &actionHandlerKey, handler, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return handler } final private class ActionHandler { var action: (() -> ())? @objc func run(_ sender: Any?) { self.action?() } } } extension NSMenu { public func addItem(title: String, image: NSImage? = nil, isSelected: Bool = false, isEnabled: Bool = true, action: (() -> Void)?) { let item = NSMenuItem(title: title, image: image, isSelected: isSelected, isEnabled: isEnabled, action: action) self.addItem(item) } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/UI/Ex+NSPopover.swift ================================================ // // Ex+NSPopover.swift // CoreUtil // // Created by yuki on 2021/05/31. // Copyright © 2021 yuki. All rights reserved. // import Cocoa import Combine extension NSPopover { public var willClosePublisher: AnyPublisher { publisherDelegate.willClosePublisher.eraseToAnyPublisher() } public var willShowPublisher: AnyPublisher { publisherDelegate.willShowPublisher.eraseToAnyPublisher() } public var didClosePublisher: AnyPublisher { publisherDelegate.didClosePublisher.eraseToAnyPublisher() } public var didShowPublisher: AnyPublisher { publisherDelegate.didShowPublisher.eraseToAnyPublisher() } public var didDetachPublisher: AnyPublisher { publisherDelegate.didDetachPublisher.eraseToAnyPublisher() } private static var delegateKey = 0 private var publisherDelegate: PublisherDelegate { if let delegate = objc_getAssociatedObject(self, &Self.delegateKey) as? PublisherDelegate { return delegate } let delegate = PublisherDelegate() self.delegate = delegate objc_setAssociatedObject(self, &Self.delegateKey, delegate, .OBJC_ASSOCIATION_RETAIN) return delegate } final private class PublisherDelegate: NSObject, NSPopoverDelegate { let willClosePublisher = PassthroughSubject() let willShowPublisher = PassthroughSubject() let didClosePublisher = PassthroughSubject() let didShowPublisher = PassthroughSubject() let didDetachPublisher = PassthroughSubject() func popoverWillClose(_ notification: Notification) { willClosePublisher.send() } func popoverWillShow(_ notification: Notification) { willShowPublisher.send() } func popoverDidDetach(_ popover: NSPopover) { didDetachPublisher.send() } func popoverDidShow(_ notification: Notification) { didShowPublisher.send() } func popoverDidClose(_ notification: Notification) { didClosePublisher.send() } } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/UI/Ex+NSTableView.swift ================================================ // // Ex+NSTableView.swift // CoreUtil // // Created by yuki on 2022/01/29. // import Cocoa extension NSTableView { public static let listColumnIdentifier = NSUserInterfaceItemIdentifier("$_column") public func becomeListStyle() { let column = NSTableColumn(identifier: Self.listColumnIdentifier) column.resizingMask = .autoresizingMask // column self.addTableColumn(column) self.columnAutoresizingStyle = .firstColumnOnlyAutoresizingStyle self.allowsColumnResizing = false self.allowsColumnSelection = false // appearance self.headerView = nil self.intercellSpacing = .zero self.focusRingType = .none // behavior self.allowsEmptySelection = true self.allowsMultipleSelection = false self.backgroundColor = .clear if #available(OSX 11.0, *) { self.style = .plain } } /// 1コラムのみのTableView public static func list() -> Self { let tableView = Self() tableView.becomeListStyle() return tableView } } ================================================ FILE: CoreUtil/CoreUtil/Extensions/UI/Ex+UI.swift ================================================ import Cocoa public let NSOutlineViewNotificationItemKey = "NSObject" extension NSView { #if DEBUG public func __setBackgroundColor(_ color: NSColor) { self.wantsLayer = true self.layer?.backgroundColor = color.cgColor } #endif } ================================================ FILE: CoreUtil/CoreUtil/HotKey/HotKey.swift ================================================ // // KeyboardShortcut.swift // ListView // // Created by yuki on 2021/06/28. // import Carbon import Cocoa public struct HotKey: Hashable { public let key: Key? public let modifiers: NSEvent.ModifierFlags public init(_ key: Key?, _ modifiers: NSEvent.ModifierFlags = []) { self.key = key self.modifiers = modifiers } } extension HotKey { public static let open = HotKey(.o, .command) public static let copy = HotKey(.c, .command) public static let paste = HotKey(.v, .command) public static let cut = HotKey(.x, .command) public static let duplicate = HotKey(.d, .command) public static let save = HotKey(.s, .command) public static let undo = HotKey(.z, .command) public static let selectAll = HotKey(.a, .command) public static let redo = HotKey(.z, [.command, .shift]) public static let go = HotKey(.rightBracket, .command) public static let back = HotKey(.leftBracket, .command) public static let print = HotKey(.p, .command) public static let delete = HotKey(.delete) public static let tab = HotKey(.tab) public static let backtab = HotKey(.tab, .shift) public static let escape = HotKey(.escape) public static let enter = HotKey(.keypadEnter) public static let space = HotKey(.space) public static let `return` = HotKey(.return) public static let leftArrow = HotKey(.leftArrow, [.numericPad, .function]) public static let rightArrow = HotKey(.rightArrow, [.numericPad, .function]) public static let upArrow = HotKey(.upArrow, [.numericPad, .function]) public static let downArrow = HotKey(.downArrow, [.numericPad, .function]) public static let leftShiftArrow = HotKey(.leftArrow, [.numericPad, .function, .shift]) public static let rightShiftArrow = HotKey(.rightArrow, [.numericPad, .function, .shift]) public static let upShiftArrow = HotKey(.upArrow, [.numericPad, .function, .shift]) public static let downShiftArrow = HotKey(.downArrow, [.numericPad, .function, .shift]) } extension HotKey: CustomStringConvertible { public var description: String { var output = modifiers.description if let key = key { output += key.description } return output } } extension NSEvent.ModifierFlags: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(rawValue) } } ================================================ FILE: CoreUtil/CoreUtil/HotKey/Key.swift ================================================ // // Key.swift // ListView // // Created by yuki on 2021/06/28. // import Carbon public enum Key { // MARK: - Letters - case a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z // MARK: - Numbers - case zero, one, two, three, four, five, six, seven, eight, nine // MARK: - Symbols - case period, quote, rightBracket, semicolon, slash, backslash, comma, equal, grave, leftBracket, minus // MARK: - White Spaces - case space, tab, `return` // MARK: - Modifiers - case command, rightCommand, option, rightOption, control, rightControl, shift, rightShift, function, capsLock // MARK: - Navigation case pageUp, pageDown, home, end, upArrow, rightArrow, downArrow, leftArrow // MARK: - Functions case f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19, f20 // MARK: - Keypad case keypad0, keypad1, keypad2, keypad3, keypad4, keypad5, keypad6, keypad7, keypad8, keypad9, keypadClear, keypadDecimal, keypadDivide, keypadEnter, keypadEquals, keypadMinus, keypadMultiply, keypadPlus // MARK: - Misc case escape, delete, forwardDelete, help, volumeUp, volumeDown, mute public init?(keyCode: Int) { switch keyCode { case kVK_ANSI_A: self = .a case kVK_ANSI_S: self = .s case kVK_ANSI_D: self = .d case kVK_ANSI_F: self = .f case kVK_ANSI_H: self = .h case kVK_ANSI_G: self = .g case kVK_ANSI_Z: self = .z case kVK_ANSI_X: self = .x case kVK_ANSI_C: self = .c case kVK_ANSI_V: self = .v case kVK_ANSI_B: self = .b case kVK_ANSI_Q: self = .q case kVK_ANSI_W: self = .w case kVK_ANSI_E: self = .e case kVK_ANSI_R: self = .r case kVK_ANSI_Y: self = .y case kVK_ANSI_T: self = .t case kVK_ANSI_1: self = .one case kVK_ANSI_2: self = .two case kVK_ANSI_3: self = .three case kVK_ANSI_4: self = .four case kVK_ANSI_6: self = .six case kVK_ANSI_5: self = .five case kVK_ANSI_Equal: self = .equal case kVK_ANSI_9: self = .nine case kVK_ANSI_7: self = .seven case kVK_ANSI_Minus: self = .minus case kVK_ANSI_8: self = .eight case kVK_ANSI_0: self = .zero case kVK_ANSI_RightBracket: self = .rightBracket case kVK_ANSI_O: self = .o case kVK_ANSI_U: self = .u case kVK_ANSI_LeftBracket: self = .leftBracket case kVK_ANSI_I: self = .i case kVK_ANSI_P: self = .p case kVK_ANSI_L: self = .l case kVK_ANSI_J: self = .j case kVK_ANSI_Quote: self = .quote case kVK_ANSI_K: self = .k case kVK_ANSI_Semicolon: self = .semicolon case kVK_ANSI_Backslash: self = .backslash case kVK_ANSI_Comma: self = .comma case kVK_ANSI_Slash: self = .slash case kVK_ANSI_N: self = .n case kVK_ANSI_M: self = .m case kVK_ANSI_Period: self = .period case kVK_ANSI_Grave: self = .grave case kVK_ANSI_KeypadDecimal: self = .keypadDecimal case kVK_ANSI_KeypadMultiply: self = .keypadMultiply case kVK_ANSI_KeypadPlus: self = .keypadPlus case kVK_ANSI_KeypadClear: self = .keypadClear case kVK_ANSI_KeypadDivide: self = .keypadDivide case kVK_ANSI_KeypadEnter: self = .keypadEnter case kVK_ANSI_KeypadMinus: self = .keypadMinus case kVK_ANSI_KeypadEquals: self = .keypadEquals case kVK_ANSI_Keypad0: self = .keypad0 case kVK_ANSI_Keypad1: self = .keypad1 case kVK_ANSI_Keypad2: self = .keypad2 case kVK_ANSI_Keypad3: self = .keypad3 case kVK_ANSI_Keypad4: self = .keypad4 case kVK_ANSI_Keypad5: self = .keypad5 case kVK_ANSI_Keypad6: self = .keypad6 case kVK_ANSI_Keypad7: self = .keypad7 case kVK_ANSI_Keypad8: self = .keypad8 case kVK_ANSI_Keypad9: self = .keypad9 case kVK_Return: self = .`return` case kVK_Tab: self = .tab case kVK_Space: self = .space case kVK_Delete: self = .delete case kVK_Escape: self = .escape case kVK_Command: self = .command case kVK_Shift: self = .shift case kVK_CapsLock: self = .capsLock case kVK_Option: self = .option case kVK_Control: self = .control case kVK_RightCommand: self = .rightCommand case kVK_RightShift: self = .rightShift case kVK_RightOption: self = .rightOption case kVK_RightControl: self = .rightControl case kVK_Function: self = .function case kVK_F17: self = .f17 case kVK_VolumeUp: self = .volumeUp case kVK_VolumeDown: self = .volumeDown case kVK_Mute: self = .mute case kVK_F18: self = .f18 case kVK_F19: self = .f19 case kVK_F20: self = .f20 case kVK_F5: self = .f5 case kVK_F6: self = .f6 case kVK_F7: self = .f7 case kVK_F3: self = .f3 case kVK_F8: self = .f8 case kVK_F9: self = .f9 case kVK_F11: self = .f11 case kVK_F13: self = .f13 case kVK_F16: self = .f16 case kVK_F14: self = .f14 case kVK_F10: self = .f10 case kVK_F12: self = .f12 case kVK_F15: self = .f15 case kVK_Help: self = .help case kVK_Home: self = .home case kVK_PageUp: self = .pageUp case kVK_ForwardDelete: self = .forwardDelete case kVK_F4: self = .f4 case kVK_End: self = .end case kVK_F2: self = .f2 case kVK_PageDown: self = .pageDown case kVK_F1: self = .f1 case kVK_LeftArrow: self = .leftArrow case kVK_RightArrow: self = .rightArrow case kVK_DownArrow: self = .downArrow case kVK_UpArrow: self = .upArrow default: return nil } } public var keyCode: Int { switch self { case .a: return kVK_ANSI_A case .s: return kVK_ANSI_S case .d: return kVK_ANSI_D case .f: return kVK_ANSI_F case .h: return kVK_ANSI_H case .g: return kVK_ANSI_G case .z: return kVK_ANSI_Z case .x: return kVK_ANSI_X case .c: return kVK_ANSI_C case .v: return kVK_ANSI_V case .b: return kVK_ANSI_B case .q: return kVK_ANSI_Q case .w: return kVK_ANSI_W case .e: return kVK_ANSI_E case .r: return kVK_ANSI_R case .y: return kVK_ANSI_Y case .t: return kVK_ANSI_T case .one: return kVK_ANSI_1 case .two: return kVK_ANSI_2 case .three: return kVK_ANSI_3 case .four: return kVK_ANSI_4 case .six: return kVK_ANSI_6 case .five: return kVK_ANSI_5 case .equal: return kVK_ANSI_Equal case .nine: return kVK_ANSI_9 case .seven: return kVK_ANSI_7 case .minus: return kVK_ANSI_Minus case .eight: return kVK_ANSI_8 case .zero: return kVK_ANSI_0 case .rightBracket: return kVK_ANSI_RightBracket case .o: return kVK_ANSI_O case .u: return kVK_ANSI_U case .leftBracket: return kVK_ANSI_LeftBracket case .i: return kVK_ANSI_I case .p: return kVK_ANSI_P case .l: return kVK_ANSI_L case .j: return kVK_ANSI_J case .quote: return kVK_ANSI_Quote case .k: return kVK_ANSI_K case .semicolon: return kVK_ANSI_Semicolon case .backslash: return kVK_ANSI_Backslash case .comma: return kVK_ANSI_Comma case .slash: return kVK_ANSI_Slash case .n: return kVK_ANSI_N case .m: return kVK_ANSI_M case .period: return kVK_ANSI_Period case .grave: return kVK_ANSI_Grave case .keypadDecimal: return kVK_ANSI_KeypadDecimal case .keypadMultiply: return kVK_ANSI_KeypadMultiply case .keypadPlus: return kVK_ANSI_KeypadPlus case .keypadClear: return kVK_ANSI_KeypadClear case .keypadDivide: return kVK_ANSI_KeypadDivide case .keypadEnter: return kVK_ANSI_KeypadEnter case .keypadMinus: return kVK_ANSI_KeypadMinus case .keypadEquals: return kVK_ANSI_KeypadEquals case .keypad0: return kVK_ANSI_Keypad0 case .keypad1: return kVK_ANSI_Keypad1 case .keypad2: return kVK_ANSI_Keypad2 case .keypad3: return kVK_ANSI_Keypad3 case .keypad4: return kVK_ANSI_Keypad4 case .keypad5: return kVK_ANSI_Keypad5 case .keypad6: return kVK_ANSI_Keypad6 case .keypad7: return kVK_ANSI_Keypad7 case .keypad8: return kVK_ANSI_Keypad8 case .keypad9: return kVK_ANSI_Keypad9 case .`return`: return kVK_Return case .tab: return kVK_Tab case .space: return kVK_Space case .delete: return kVK_Delete case .escape: return kVK_Escape case .command: return kVK_Command case .shift: return kVK_Shift case .capsLock: return kVK_CapsLock case .option: return kVK_Option case .control: return kVK_Control case .rightCommand: return kVK_RightCommand case .rightShift: return kVK_RightShift case .rightOption: return kVK_RightOption case .rightControl: return kVK_RightControl case .function: return kVK_Function case .f17: return kVK_F17 case .volumeUp: return kVK_VolumeUp case .volumeDown: return kVK_VolumeDown case .mute: return kVK_Mute case .f18: return kVK_F18 case .f19: return kVK_F19 case .f20: return kVK_F20 case .f5: return kVK_F5 case .f6: return kVK_F6 case .f7: return kVK_F7 case .f3: return kVK_F3 case .f8: return kVK_F8 case .f9: return kVK_F9 case .f11: return kVK_F11 case .f13: return kVK_F13 case .f16: return kVK_F16 case .f14: return kVK_F14 case .f10: return kVK_F10 case .f12: return kVK_F12 case .f15: return kVK_F15 case .help: return kVK_Help case .home: return kVK_Home case .pageUp: return kVK_PageUp case .forwardDelete: return kVK_ForwardDelete case .f4: return kVK_F4 case .end: return kVK_End case .f2: return kVK_F2 case .pageDown: return kVK_PageDown case .f1: return kVK_F1 case .leftArrow: return kVK_LeftArrow case .rightArrow: return kVK_RightArrow case .downArrow: return kVK_DownArrow case .upArrow: return kVK_UpArrow } } } extension Key: CustomStringConvertible { public var description: String { switch self { case .a: return "A" case .s: return "S" case .d: return "D" case .f: return "F" case .h: return "H" case .g: return "G" case .z: return "Z" case .x: return "X" case .c: return "C" case .v: return "V" case .b: return "B" case .q: return "Q" case .w: return "W" case .e: return "E" case .r: return "R" case .y: return "Y" case .t: return "T" case .one, .keypad1: return "1" case .two, .keypad2: return "2" case .three, .keypad3: return "3" case .four, .keypad4: return "4" case .six, .keypad6: return "6" case .five, .keypad5: return "5" case .equal: return "=" case .nine, .keypad9: return "9" case .seven, .keypad7: return "7" case .minus: return "-" case .eight, .keypad8: return "8" case .zero, .keypad0: return "0" case .rightBracket: return "]" case .o: return "O" case .u: return "U" case .leftBracket: return "[" case .i: return "I" case .p: return "P" case .l: return "L" case .j: return "J" case .quote: return "\"" case .k: return "K" case .semicolon: return ";" case .backslash: return "\\" case .comma: return "," case .slash: return "/" case .n: return "N" case .m: return "M" case .period: return "." case .grave: return "`" case .keypadDecimal: return "." case .keypadMultiply: return "𝗑" case .keypadPlus: return "+" case .keypadClear: return "⌧" case .keypadDivide: return "/" case .keypadEnter: return "↩︎" case .keypadMinus: return "-" case .keypadEquals: return "=" case .`return`: return "↩︎" case .tab: return "⇥" case .space: return "␣" case .delete: return "⌫" case .escape: return "⎋" case .command, .rightCommand: return "⌘" case .shift, .rightShift: return "⇧" case .capsLock: return "⇪" case .option, .rightOption: return "⌥" case .control, .rightControl: return "⌃" case .function: return "fn" case .f17: return "F17" case .volumeUp: return "🔊" case .volumeDown: return "🔉" case .mute: return "🔇" case .f18: return "F18" case .f19: return "F19" case .f20: return "F20" case .f5: return "F5" case .f6: return "F6" case .f7: return "F7" case .f3: return "F3" case .f8: return "F8" case .f9: return "F9" case .f11: return "F11" case .f13: return "F13" case .f16: return "F16" case .f14: return "F14" case .f10: return "F10" case .f12: return "F12" case .f15: return "F15" case .help: return "?⃝" case .home: return "↖" case .pageUp: return "⇞" case .forwardDelete: return "⌦" case .f4: return "F4" case .end: return "↘" case .f2: return "F2" case .pageDown: return "⇟" case .f1: return "F1" case .leftArrow: return "←" case .rightArrow: return "→" case .downArrow: return "↓" case .upArrow: return "↑" } } } ================================================ FILE: CoreUtil/CoreUtil/HotKey/NSEvent+HotKey.swift ================================================ // // NSEvent+HotKey.swift // ListView // // Created by yuki on 2021/06/28. // import Cocoa extension NSEvent { public var hotKey: HotKey { HotKey(Key(keyCode: Int(self.keyCode)), self.modifierFlags.intersection(.deviceIndependentFlagsMask)) } } extension NSEvent.ModifierFlags: CustomStringConvertible { public var description: String { var output = "" if self.contains(.capsLock) { output += Key.capsLock.description } if self.contains(.shift) { output += Key.shift.description } if self.contains(.control) { output += Key.control.description } if self.contains(.option) { output += Key.option.description } if self.contains(.command) { output += Key.command.description } if self.contains(.numericPad) { output += "Np" } if self.contains(.help) { output += Key.help.description } if self.contains(.function) { output += Key.function.description } return output } } ================================================ FILE: CoreUtil/CoreUtil/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) ================================================ FILE: CoreUtil/CoreUtil.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 52; objects = { /* Begin PBXBuildFile section */ B608540827A66635003BF243 /* NSControl+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = B608540727A66635003BF243 /* NSControl+Combine.swift */; }; B62013E927A9147600AF5386 /* Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62013E827A9147600AF5386 /* Delta.swift */; }; B64B1E9427A4F67000AC2601 /* CoreUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = B64B1E9227A4F67000AC2601 /* CoreUtil.h */; settings = {ATTRIBUTES = (Public, ); }; }; B64B1ED027A4F71200AC2601 /* ViewPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1ECF27A4F71200AC2601 /* ViewPlaceholder.swift */; }; B64B1ED327A4F71F00AC2601 /* NS+OnAwake.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1ED227A4F71F00AC2601 /* NS+OnAwake.swift */; }; B64B1ED627A4F72D00AC2601 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1ED527A4F72D00AC2601 /* Query.swift */; }; B64B1ED927A4F73700AC2601 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1ED827A4F73700AC2601 /* Observable.swift */; }; B64B1EDC27A4F75600AC2601 /* PipeOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1EDB27A4F75600AC2601 /* PipeOperator.swift */; }; B64B1EE327A4F79100AC2601 /* NSEvent+HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1EE027A4F79100AC2601 /* NSEvent+HotKey.swift */; }; B64B1EE427A4F79100AC2601 /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1EE127A4F79100AC2601 /* Key.swift */; }; B64B1EE527A4F79100AC2601 /* HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1EE227A4F79100AC2601 /* HotKey.swift */; }; B64B1EF727A4F7AA00AC2601 /* Ex+Number.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1EE927A4F7AA00AC2601 /* Ex+Number.swift */; }; B64B1EFA27A4F7AA00AC2601 /* Ex+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1EEC27A4F7AA00AC2601 /* Ex+Array.swift */; }; B64B1F0027A4F7AA00AC2601 /* Ex+OptionSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1EF227A4F7AA00AC2601 /* Ex+OptionSet.swift */; }; B64B1F0127A4F7AA00AC2601 /* Ex+Clamp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1EF327A4F7AA00AC2601 /* Ex+Clamp.swift */; }; B64B1F1627A4F80800AC2601 /* Ex+CGSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F1127A4F80800AC2601 /* Ex+CGSize.swift */; }; B64B1F1827A4F80800AC2601 /* Ex+CGPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F1327A4F80800AC2601 /* Ex+CGPoint.swift */; }; B64B1F1A27A4F80800AC2601 /* Ex+CGRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F1527A4F80800AC2601 /* Ex+CGRect.swift */; }; B64B1F2B27A4F83500AC2601 /* Ex+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F1F27A4F83500AC2601 /* Ex+UI.swift */; }; B64B1F2E27A4F83500AC2601 /* Ex+NSControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F2227A4F83500AC2601 /* Ex+NSControl.swift */; }; B64B1F3027A4F83500AC2601 /* Ex+NSPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F2427A4F83500AC2601 /* Ex+NSPopover.swift */; }; B64B1F3127A4F83500AC2601 /* Ex+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F2527A4F83500AC2601 /* Ex+Image.swift */; }; B64B1F3327A4F83500AC2601 /* Ex+NSEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F2727A4F83500AC2601 /* Ex+NSEvent.swift */; }; B64B1F3427A4F83500AC2601 /* Ex+NSMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F2827A4F83500AC2601 /* Ex+NSMenuItem.swift */; }; B64B1F3627A4F83500AC2601 /* Ex+NSColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F2A27A4F83500AC2601 /* Ex+NSColor.swift */; }; B64B1F3927A4F8AD00AC2601 /* Ex+NSTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F3827A4F8AD00AC2601 /* Ex+NSTableView.swift */; }; B64B1F3C27A4F8C400AC2601 /* Ex+NSEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F3B27A4F8C400AC2601 /* Ex+NSEdgeInsets.swift */; }; B64B1F6327A4FC5100AC2601 /* Promise in Frameworks */ = {isa = PBXBuildFile; productRef = B64B1F6227A4FC5100AC2601 /* Promise */; }; B64B1F6827A4FC8A00AC2601 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = B64B1F6727A4FC8A00AC2601 /* SnapKit */; }; B64B1F6B27A4FC8F00AC2601 /* Export.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F6A27A4FC8F00AC2601 /* Export.swift */; }; B64B201127A50E5200AC2601 /* NSColorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B201027A50E5200AC2601 /* NSColorView.swift */; }; B64B201A27A5103100AC2601 /* Ex+CALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B201927A5103100AC2601 /* Ex+CALayer.swift */; }; B64B205927A530D700AC2601 /* NSViewController+ChainObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B205827A530D700AC2601 /* NSViewController+ChainObject.swift */; }; B64B205C27A530E800AC2601 /* NSViewController+StateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B205B27A530E800AC2601 /* NSViewController+StateObject.swift */; }; B64B209527A5323A00AC2601 /* Combine+ObjectBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B209427A5323A00AC2601 /* Combine+ObjectBag.swift */; }; B66F0D4C27B6176A00DC812D /* Terminal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66F0D4B27B6176A00DC812D /* Terminal.swift */; }; B680A79127A681F8007CB707 /* NSTextField+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = B680A79027A681F8007CB707 /* NSTextField+Combine.swift */; }; B680A86527A8C58C007CB707 /* Combine+Peek.swift in Sources */ = {isa = PBXBuildFile; fileRef = B680A86427A8C58C007CB707 /* Combine+Peek.swift */; }; B680A89C27A8DA16007CB707 /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = B680A89B27A8DA16007CB707 /* Action.swift */; }; B680A8A027A8DA78007CB707 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = B680A89F27A8DA78007CB707 /* OrderedCollections */; }; B680A8A227A8DA78007CB707 /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = B680A8A127A8DA78007CB707 /* DequeModule */; }; B680A8A427A8DA78007CB707 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = B680A8A327A8DA78007CB707 /* Collections */; }; B6A93D2B27CBA2EB003A6D7F /* Zip3Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A93D2A27CBA2EB003A6D7F /* Zip3Sequence.swift */; }; B6AC27A327AA6F5C000FD713 /* Reachability+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AC27A127AA6F5B000FD713 /* Reachability+Publisher.swift */; }; B6AC27A427AA6F5C000FD713 /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AC27A227AA6F5C000FD713 /* Reachability.swift */; }; B6B5727A27AC223A0069DBA7 /* RestorableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5727927AC223A0069DBA7 /* RestorableState.swift */; }; B6B5727D27AC22480069DBA7 /* RestorableData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5727C27AC22480069DBA7 /* RestorableData.swift */; }; B6BD80FA27B8B06900152AB9 /* Ex+Localize.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BD80F927B8B06900152AB9 /* Ex+Localize.swift */; }; B6D1AF3D27A60A210022FED2 /* ExceptionHanlder.m in Sources */ = {isa = PBXBuildFile; fileRef = B6D1AF3A27A60A210022FED2 /* ExceptionHanlder.m */; }; B6D1AF3E27A60A210022FED2 /* ExceptionHanlder.h in Headers */ = {isa = PBXBuildFile; fileRef = B6D1AF3B27A60A210022FED2 /* ExceptionHanlder.h */; settings = {ATTRIBUTES = (Public, ); }; }; B6D1AF3F27A60A210022FED2 /* ExceptionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D1AF3C27A60A210022FED2 /* ExceptionHandler.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ B608540727A66635003BF243 /* NSControl+Combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSControl+Combine.swift"; sourceTree = ""; }; B62013E827A9147600AF5386 /* Delta.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Delta.swift; sourceTree = ""; }; B64B1E8F27A4F67000AC2601 /* CoreUtil.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreUtil.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B64B1E9227A4F67000AC2601 /* CoreUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoreUtil.h; sourceTree = ""; }; B64B1E9327A4F67000AC2601 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B64B1ECF27A4F71200AC2601 /* ViewPlaceholder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewPlaceholder.swift; sourceTree = ""; }; B64B1ED227A4F71F00AC2601 /* NS+OnAwake.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NS+OnAwake.swift"; sourceTree = ""; }; B64B1ED527A4F72D00AC2601 /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; }; B64B1ED827A4F73700AC2601 /* Observable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; B64B1EDB27A4F75600AC2601 /* PipeOperator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PipeOperator.swift; sourceTree = ""; }; B64B1EE027A4F79100AC2601 /* NSEvent+HotKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSEvent+HotKey.swift"; sourceTree = ""; }; B64B1EE127A4F79100AC2601 /* Key.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Key.swift; sourceTree = ""; }; B64B1EE227A4F79100AC2601 /* HotKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotKey.swift; sourceTree = ""; }; B64B1EE927A4F7AA00AC2601 /* Ex+Number.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+Number.swift"; sourceTree = ""; }; B64B1EEC27A4F7AA00AC2601 /* Ex+Array.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+Array.swift"; sourceTree = ""; }; B64B1EF227A4F7AA00AC2601 /* Ex+OptionSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+OptionSet.swift"; sourceTree = ""; }; B64B1EF327A4F7AA00AC2601 /* Ex+Clamp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+Clamp.swift"; sourceTree = ""; }; B64B1F1127A4F80800AC2601 /* Ex+CGSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+CGSize.swift"; sourceTree = ""; }; B64B1F1327A4F80800AC2601 /* Ex+CGPoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+CGPoint.swift"; sourceTree = ""; }; B64B1F1527A4F80800AC2601 /* Ex+CGRect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+CGRect.swift"; sourceTree = ""; }; B64B1F1F27A4F83500AC2601 /* Ex+UI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+UI.swift"; sourceTree = ""; }; B64B1F2227A4F83500AC2601 /* Ex+NSControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+NSControl.swift"; sourceTree = ""; }; B64B1F2427A4F83500AC2601 /* Ex+NSPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+NSPopover.swift"; sourceTree = ""; }; B64B1F2527A4F83500AC2601 /* Ex+Image.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+Image.swift"; sourceTree = ""; }; B64B1F2727A4F83500AC2601 /* Ex+NSEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+NSEvent.swift"; sourceTree = ""; }; B64B1F2827A4F83500AC2601 /* Ex+NSMenuItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+NSMenuItem.swift"; sourceTree = ""; }; B64B1F2A27A4F83500AC2601 /* Ex+NSColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+NSColor.swift"; sourceTree = ""; }; B64B1F3827A4F8AD00AC2601 /* Ex+NSTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Ex+NSTableView.swift"; sourceTree = ""; }; B64B1F3B27A4F8C400AC2601 /* Ex+NSEdgeInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Ex+NSEdgeInsets.swift"; sourceTree = ""; }; B64B1F6A27A4FC8F00AC2601 /* Export.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Export.swift; sourceTree = ""; }; B64B201027A50E5200AC2601 /* NSColorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSColorView.swift; sourceTree = ""; }; B64B201927A5103100AC2601 /* Ex+CALayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+CALayer.swift"; sourceTree = ""; }; B64B205827A530D700AC2601 /* NSViewController+ChainObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSViewController+ChainObject.swift"; sourceTree = ""; }; B64B205B27A530E800AC2601 /* NSViewController+StateObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSViewController+StateObject.swift"; sourceTree = ""; }; B64B209427A5323A00AC2601 /* Combine+ObjectBag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Combine+ObjectBag.swift"; sourceTree = ""; }; B66F0D4B27B6176A00DC812D /* Terminal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Terminal.swift; sourceTree = ""; }; B680A79027A681F8007CB707 /* NSTextField+Combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTextField+Combine.swift"; sourceTree = ""; }; B680A86427A8C58C007CB707 /* Combine+Peek.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Combine+Peek.swift"; sourceTree = ""; }; B680A89B27A8DA16007CB707 /* Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = ""; }; B6A93D2A27CBA2EB003A6D7F /* Zip3Sequence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Zip3Sequence.swift; sourceTree = ""; }; B6AC27A127AA6F5B000FD713 /* Reachability+Publisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Reachability+Publisher.swift"; sourceTree = ""; }; B6AC27A227AA6F5C000FD713 /* Reachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = ""; }; B6B5727927AC223A0069DBA7 /* RestorableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorableState.swift; sourceTree = ""; }; B6B5727C27AC22480069DBA7 /* RestorableData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorableData.swift; sourceTree = ""; }; B6BD80F927B8B06900152AB9 /* Ex+Localize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Ex+Localize.swift"; sourceTree = ""; }; B6D1AF3A27A60A210022FED2 /* ExceptionHanlder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExceptionHanlder.m; sourceTree = ""; }; B6D1AF3B27A60A210022FED2 /* ExceptionHanlder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExceptionHanlder.h; sourceTree = ""; }; B6D1AF3C27A60A210022FED2 /* ExceptionHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExceptionHandler.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ B64B1E8C27A4F67000AC2601 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( B64B1F6327A4FC5100AC2601 /* Promise in Frameworks */, B680A8A227A8DA78007CB707 /* DequeModule in Frameworks */, B680A8A427A8DA78007CB707 /* Collections in Frameworks */, B64B1F6827A4FC8A00AC2601 /* SnapKit in Frameworks */, B680A8A027A8DA78007CB707 /* OrderedCollections in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ B64B1E8527A4F67000AC2601 = { isa = PBXGroup; children = ( B64B1E9127A4F67000AC2601 /* CoreUtil */, B64B1E9027A4F67000AC2601 /* Products */, ); sourceTree = ""; }; B64B1E9027A4F67000AC2601 /* Products */ = { isa = PBXGroup; children = ( B64B1E8F27A4F67000AC2601 /* CoreUtil.framework */, ); name = Products; sourceTree = ""; }; B64B1E9127A4F67000AC2601 /* CoreUtil */ = { isa = PBXGroup; children = ( B64B1E9227A4F67000AC2601 /* CoreUtil.h */, B64B1E9327A4F67000AC2601 /* Info.plist */, B64B1F6A27A4FC8F00AC2601 /* Export.swift */, B6D1AF3927A60A210022FED2 /* ExceptionHanlder */, B64B1F6D27A4FC9100AC2601 /* Class */, B64B1EE727A4F79800AC2601 /* Extensions */, B64B1EDF27A4F79100AC2601 /* HotKey */, ); path = CoreUtil; sourceTree = ""; }; B64B1EDF27A4F79100AC2601 /* HotKey */ = { isa = PBXGroup; children = ( B64B1EE027A4F79100AC2601 /* NSEvent+HotKey.swift */, B64B1EE127A4F79100AC2601 /* Key.swift */, B64B1EE227A4F79100AC2601 /* HotKey.swift */, ); path = HotKey; sourceTree = ""; }; B64B1EE727A4F79800AC2601 /* Extensions */ = { isa = PBXGroup; children = ( B64B208E27A5322A00AC2601 /* Combine */, B64B1F1E27A4F83500AC2601 /* UI */, B64B1F1027A4F80800AC2601 /* CoreGraphics */, B64B1EE827A4F7AA00AC2601 /* Swift */, ); path = Extensions; sourceTree = ""; }; B64B1EE827A4F7AA00AC2601 /* Swift */ = { isa = PBXGroup; children = ( B64B1EE927A4F7AA00AC2601 /* Ex+Number.swift */, B64B1EEC27A4F7AA00AC2601 /* Ex+Array.swift */, B64B1EF227A4F7AA00AC2601 /* Ex+OptionSet.swift */, B64B1EF327A4F7AA00AC2601 /* Ex+Clamp.swift */, B6BD80F927B8B06900152AB9 /* Ex+Localize.swift */, ); path = Swift; sourceTree = ""; }; B64B1F1027A4F80800AC2601 /* CoreGraphics */ = { isa = PBXGroup; children = ( B64B1F1127A4F80800AC2601 /* Ex+CGSize.swift */, B64B1F1327A4F80800AC2601 /* Ex+CGPoint.swift */, B64B1F1527A4F80800AC2601 /* Ex+CGRect.swift */, ); path = CoreGraphics; sourceTree = ""; }; B64B1F1E27A4F83500AC2601 /* UI */ = { isa = PBXGroup; children = ( B64B201927A5103100AC2601 /* Ex+CALayer.swift */, B64B1F1F27A4F83500AC2601 /* Ex+UI.swift */, B64B1F2227A4F83500AC2601 /* Ex+NSControl.swift */, B64B1F2427A4F83500AC2601 /* Ex+NSPopover.swift */, B64B1F2527A4F83500AC2601 /* Ex+Image.swift */, B64B1F2727A4F83500AC2601 /* Ex+NSEvent.swift */, B64B1F2827A4F83500AC2601 /* Ex+NSMenuItem.swift */, B64B1F2A27A4F83500AC2601 /* Ex+NSColor.swift */, B64B1F3827A4F8AD00AC2601 /* Ex+NSTableView.swift */, B64B1F3B27A4F8C400AC2601 /* Ex+NSEdgeInsets.swift */, ); path = UI; sourceTree = ""; }; B64B1F6D27A4FC9100AC2601 /* Class */ = { isa = PBXGroup; children = ( B6A93D2A27CBA2EB003A6D7F /* Zip3Sequence.swift */, B6AC27A227AA6F5C000FD713 /* Reachability.swift */, B6AC27A127AA6F5B000FD713 /* Reachability+Publisher.swift */, B64B201027A50E5200AC2601 /* NSColorView.swift */, B64B1ECF27A4F71200AC2601 /* ViewPlaceholder.swift */, B64B1ED527A4F72D00AC2601 /* Query.swift */, B680A89B27A8DA16007CB707 /* Action.swift */, B64B205827A530D700AC2601 /* NSViewController+ChainObject.swift */, B64B205B27A530E800AC2601 /* NSViewController+StateObject.swift */, B62013E827A9147600AF5386 /* Delta.swift */, B64B1ED827A4F73700AC2601 /* Observable.swift */, B64B1EDB27A4F75600AC2601 /* PipeOperator.swift */, B64B1ED227A4F71F00AC2601 /* NS+OnAwake.swift */, B6B5727927AC223A0069DBA7 /* RestorableState.swift */, B6B5727C27AC22480069DBA7 /* RestorableData.swift */, B66F0D4B27B6176A00DC812D /* Terminal.swift */, ); path = Class; sourceTree = ""; }; B64B208E27A5322A00AC2601 /* Combine */ = { isa = PBXGroup; children = ( B608540727A66635003BF243 /* NSControl+Combine.swift */, B680A79027A681F8007CB707 /* NSTextField+Combine.swift */, B64B209427A5323A00AC2601 /* Combine+ObjectBag.swift */, B680A86427A8C58C007CB707 /* Combine+Peek.swift */, ); path = Combine; sourceTree = ""; }; B6D1AF3927A60A210022FED2 /* ExceptionHanlder */ = { isa = PBXGroup; children = ( B6D1AF3A27A60A210022FED2 /* ExceptionHanlder.m */, B6D1AF3B27A60A210022FED2 /* ExceptionHanlder.h */, B6D1AF3C27A60A210022FED2 /* ExceptionHandler.swift */, ); path = ExceptionHanlder; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ B64B1E8A27A4F67000AC2601 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( B64B1E9427A4F67000AC2601 /* CoreUtil.h in Headers */, B6D1AF3E27A60A210022FED2 /* ExceptionHanlder.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ B64B1E8E27A4F67000AC2601 /* CoreUtil */ = { isa = PBXNativeTarget; buildConfigurationList = B64B1E9727A4F67000AC2601 /* Build configuration list for PBXNativeTarget "CoreUtil" */; buildPhases = ( B64B1E8A27A4F67000AC2601 /* Headers */, B64B1E8B27A4F67000AC2601 /* Sources */, B64B1E8C27A4F67000AC2601 /* Frameworks */, B64B1E8D27A4F67000AC2601 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = CoreUtil; packageProductDependencies = ( B64B1F6227A4FC5100AC2601 /* Promise */, B64B1F6727A4FC8A00AC2601 /* SnapKit */, B680A89F27A8DA78007CB707 /* OrderedCollections */, B680A8A127A8DA78007CB707 /* DequeModule */, B680A8A327A8DA78007CB707 /* Collections */, ); productName = CoreUtil; productReference = B64B1E8F27A4F67000AC2601 /* CoreUtil.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ B64B1E8627A4F67000AC2601 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1240; TargetAttributes = { B64B1E8E27A4F67000AC2601 = { CreatedOnToolsVersion = 12.4; LastSwiftMigration = 1240; }; }; }; buildConfigurationList = B64B1E8927A4F67000AC2601 /* Build configuration list for PBXProject "CoreUtil" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = B64B1E8527A4F67000AC2601; packageReferences = ( B64B1F6127A4FC5100AC2601 /* XCRemoteSwiftPackageReference "Promise" */, B64B1F6627A4FC8A00AC2601 /* XCRemoteSwiftPackageReference "SnapKit" */, B680A89E27A8DA78007CB707 /* XCRemoteSwiftPackageReference "swift-collections" */, ); productRefGroup = B64B1E9027A4F67000AC2601 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( B64B1E8E27A4F67000AC2601 /* CoreUtil */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ B64B1E8D27A4F67000AC2601 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ B64B1E8B27A4F67000AC2601 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( B6D1AF3D27A60A210022FED2 /* ExceptionHanlder.m in Sources */, B680A89C27A8DA16007CB707 /* Action.swift in Sources */, B64B1F6B27A4FC8F00AC2601 /* Export.swift in Sources */, B64B205C27A530E800AC2601 /* NSViewController+StateObject.swift in Sources */, B608540827A66635003BF243 /* NSControl+Combine.swift in Sources */, B64B1F3327A4F83500AC2601 /* Ex+NSEvent.swift in Sources */, B64B205927A530D700AC2601 /* NSViewController+ChainObject.swift in Sources */, B66F0D4C27B6176A00DC812D /* Terminal.swift in Sources */, B64B1EE427A4F79100AC2601 /* Key.swift in Sources */, B64B1F0027A4F7AA00AC2601 /* Ex+OptionSet.swift in Sources */, B6AC27A327AA6F5C000FD713 /* Reachability+Publisher.swift in Sources */, B64B1F2E27A4F83500AC2601 /* Ex+NSControl.swift in Sources */, B64B1F1627A4F80800AC2601 /* Ex+CGSize.swift in Sources */, B6B5727A27AC223A0069DBA7 /* RestorableState.swift in Sources */, B64B1ED927A4F73700AC2601 /* Observable.swift in Sources */, B64B201127A50E5200AC2601 /* NSColorView.swift in Sources */, B64B1EE327A4F79100AC2601 /* NSEvent+HotKey.swift in Sources */, B64B201A27A5103100AC2601 /* Ex+CALayer.swift in Sources */, B680A79127A681F8007CB707 /* NSTextField+Combine.swift in Sources */, B64B1EFA27A4F7AA00AC2601 /* Ex+Array.swift in Sources */, B64B1F1A27A4F80800AC2601 /* Ex+CGRect.swift in Sources */, B64B1F3C27A4F8C400AC2601 /* Ex+NSEdgeInsets.swift in Sources */, B64B1F3427A4F83500AC2601 /* Ex+NSMenuItem.swift in Sources */, B64B1F3027A4F83500AC2601 /* Ex+NSPopover.swift in Sources */, B64B1EE527A4F79100AC2601 /* HotKey.swift in Sources */, B64B1F3627A4F83500AC2601 /* Ex+NSColor.swift in Sources */, B64B209527A5323A00AC2601 /* Combine+ObjectBag.swift in Sources */, B64B1EDC27A4F75600AC2601 /* PipeOperator.swift in Sources */, B6BD80FA27B8B06900152AB9 /* Ex+Localize.swift in Sources */, B64B1F1827A4F80800AC2601 /* Ex+CGPoint.swift in Sources */, B680A86527A8C58C007CB707 /* Combine+Peek.swift in Sources */, B64B1ED027A4F71200AC2601 /* ViewPlaceholder.swift in Sources */, B6A93D2B27CBA2EB003A6D7F /* Zip3Sequence.swift in Sources */, B64B1F3127A4F83500AC2601 /* Ex+Image.swift in Sources */, B64B1ED627A4F72D00AC2601 /* Query.swift in Sources */, B64B1F2B27A4F83500AC2601 /* Ex+UI.swift in Sources */, B64B1F3927A4F8AD00AC2601 /* Ex+NSTableView.swift in Sources */, B64B1F0127A4F7AA00AC2601 /* Ex+Clamp.swift in Sources */, B64B1EF727A4F7AA00AC2601 /* Ex+Number.swift in Sources */, B62013E927A9147600AF5386 /* Delta.swift in Sources */, B6D1AF3F27A60A210022FED2 /* ExceptionHandler.swift in Sources */, B64B1ED327A4F71F00AC2601 /* NS+OnAwake.swift in Sources */, B6AC27A427AA6F5C000FD713 /* Reachability.swift in Sources */, B6B5727D27AC22480069DBA7 /* RestorableData.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ B64B1E9527A4F67000AC2601 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 11.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; B64B1E9627A4F67000AC2601 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 11.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; B64B1E9827A4F67000AC2601 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = T5HMUWWK5R; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreUtil/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = com.yuki.CoreUtil; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_ENFORCE_EXCLUSIVE_ACCESS = off; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; B64B1E9927A4F67000AC2601 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = T5HMUWWK5R; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = CoreUtil/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = com.yuki.CoreUtil; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_ENFORCE_EXCLUSIVE_ACCESS = off; SWIFT_VERSION = 5.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ B64B1E8927A4F67000AC2601 /* Build configuration list for PBXProject "CoreUtil" */ = { isa = XCConfigurationList; buildConfigurations = ( B64B1E9527A4F67000AC2601 /* Debug */, B64B1E9627A4F67000AC2601 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; B64B1E9727A4F67000AC2601 /* Build configuration list for PBXNativeTarget "CoreUtil" */ = { isa = XCConfigurationList; buildConfigurations = ( B64B1E9827A4F67000AC2601 /* Debug */, B64B1E9927A4F67000AC2601 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ B64B1F6127A4FC5100AC2601 /* XCRemoteSwiftPackageReference "Promise" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ObuchiYuki/Promise.git"; requirement = { kind = upToNextMajorVersion; minimumVersion = 1.0.9; }; }; B64B1F6627A4FC8A00AC2601 /* XCRemoteSwiftPackageReference "SnapKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SnapKit/SnapKit.git"; requirement = { kind = upToNextMajorVersion; minimumVersion = 5.0.1; }; }; B680A89E27A8DA78007CB707 /* XCRemoteSwiftPackageReference "swift-collections" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/apple/swift-collections"; requirement = { kind = upToNextMajorVersion; minimumVersion = 1.0.2; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ B64B1F6227A4FC5100AC2601 /* Promise */ = { isa = XCSwiftPackageProductDependency; package = B64B1F6127A4FC5100AC2601 /* XCRemoteSwiftPackageReference "Promise" */; productName = Promise; }; B64B1F6727A4FC8A00AC2601 /* SnapKit */ = { isa = XCSwiftPackageProductDependency; package = B64B1F6627A4FC8A00AC2601 /* XCRemoteSwiftPackageReference "SnapKit" */; productName = SnapKit; }; B680A89F27A8DA78007CB707 /* OrderedCollections */ = { isa = XCSwiftPackageProductDependency; package = B680A89E27A8DA78007CB707 /* XCRemoteSwiftPackageReference "swift-collections" */; productName = OrderedCollections; }; B680A8A127A8DA78007CB707 /* DequeModule */ = { isa = XCSwiftPackageProductDependency; package = B680A89E27A8DA78007CB707 /* XCRemoteSwiftPackageReference "swift-collections" */; productName = DequeModule; }; B680A8A327A8DA78007CB707 /* Collections */ = { isa = XCSwiftPackageProductDependency; package = B680A89E27A8DA78007CB707 /* XCRemoteSwiftPackageReference "swift-collections" */; productName = Collections; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = B64B1E8627A4F67000AC2601 /* Project object */; } ================================================ FILE: CoreUtil/CoreUtil.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: CoreUtil/CoreUtil.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: DevToys/DevToys/App/AppViewController.swift ================================================ // // ViewController.swift // DevToys // // Created by yuki on 2022/01/29. // import Cocoa import CoreUtil final class AppViewController: NSSplitViewController { private let sidebarController = SidebarViewController() private let bodyController = BodyViewController() override func chainObjectDidLoad() { // The object chain will be broken on `addSplitViewItem`. So call the manual chain. self.sidebarController.chainObject = chainObject self.bodyController.chainObject = chainObject } override func viewDidLoad() { super.viewDidLoad() let sidebarItem = NSSplitViewItem(sidebarWithViewController: sidebarController) sidebarItem.minimumThickness = 200 sidebarItem.canCollapse = false self.addSplitViewItem(sidebarItem) let bodyItem = NSSplitViewItem(viewController: bodyController) bodyItem.minimumThickness = 480 bodyItem.canCollapse = false self.addSplitViewItem(bodyItem) splitView.setPosition(220, ofDividerAt: 0) self.splitView.autosaveName = "app.splitv" } } ================================================ FILE: DevToys/DevToys/App/AppWindowController.swift ================================================ // // AppWindowController.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil import Darwin import AppKit final class AppWindowController: NSWindowController { private let appModel = AppModel() private lazy var separatorItem = NSTrackingSeparatorToolbarItem( identifier: AppWindowController.separatorItemIdentifier, splitView: splitViewController.splitView, dividerIndex: 0 ) private lazy var disclosureItem = NSToolbarItem( itemIdentifier: AppWindowController.disclosureItemIdentifier ) private var splitViewController: NSSplitViewController { self.window?.contentViewController as! NSSplitViewController } @objc private func toggleSidebar() { splitViewController.splitViewItems[0].animator().isCollapsed.toggle() } override func windowDidLoad() { self.contentViewController?.chainObject = appModel self.appModel.settings.$appearanceType .sink{ switch $0 { case .useSystemSettings: self.window?.appearance = nil case .darkMode: self.window?.appearance = NSAppearance(named: .darkAqua) case .lightMode: self.window?.appearance = NSAppearance(named: .aqua) } } .store(in: &objectBag) self.appModel.$tool .sink{[unowned self] in self.window?.title = $0.title }.store(in: &objectBag) guard let toolbar = self.window?.toolbar else { return assertionFailure() } toolbar.delegate = self toolbar.allowsUserCustomization = true disclosureItem.view = NSButton(title: "Sidebar", image: R.Image.sidebarDisclosure) => { $0.bezelStyle = .texturedRounded $0.actionPublisher.sink{[unowned self] in toggleSidebar() }.store(in: &objectBag) } } } extension AppWindowController: NSToolbarDelegate { static let separatorItemIdentifier = NSToolbarItem.Identifier("separator") static let disclosureItemIdentifier = NSToolbarItem.Identifier("disclosure") func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { if #available(macOS 11.0, *) { return [AppWindowController.disclosureItemIdentifier, AppWindowController.separatorItemIdentifier] } else { return [AppWindowController.disclosureItemIdentifier] } } func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { self.toolbarDefaultItemIdentifiers(toolbar) } func toolbarSelectableItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { [] } func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { if #available(macOS 11.0, *) { if itemIdentifier == AppWindowController.separatorItemIdentifier { return self.separatorItem } } if itemIdentifier == AppWindowController.disclosureItemIdentifier { return self.disclosureItem } return nil } } ================================================ FILE: DevToys/DevToys/AppDelegate.swift ================================================ // // AppDelegate.swift // DevToys // // Created by yuki on 2022/01/29. // import Cocoa @main class AppDelegate: NSObject, NSApplicationDelegate { func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { true } } ================================================ FILE: DevToys/DevToys/Body/BodyViewController.swift ================================================ // // BodyViewController.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil final class BodyViewController: NSViewController { private let placeholderView = NSPlaceholderView() private var contentViewController: NSViewController? override func loadView() { self.view = placeholderView } override func chainObjectDidLoad() { self.appModel.$tool .sink{[unowned self] in replaceTool($0) }.store(in: &objectBag) self.appModel.$searchQuery .sink{[unowned self] in handleQuery($0) }.store(in: &objectBag) } private func handleQuery(_ query: String) { if query.isEmpty { self.replaceTool(appModel.tool) } else { self.replaceTool(.search) } } private func replaceTool(_ tool: Tool) { self.contentViewController?.removeFromParent() self.addChild(tool.viewController) self.contentViewController = tool.viewController self.placeholderView.contentView = tool.viewController.view } } ================================================ FILE: DevToys/DevToys/Body/Coder/Base64DecoderView+.swift ================================================ // // Base64Decoder.swift // DevToys // // Created by yuki on 2022/01/30. // import CoreUtil final class Base64DecoderViewController: NSViewController { private let cell = Base64DecoderView() @RestorableState("base64.sourceType") private var sourceType = SourceType.text @RestorableState("base64.rawString") private var rawString = defaultRawString @Observable private var formattedString = defaultBase64String override func loadView() { self.view = cell } override func viewDidLoad() { self.cell.inputTextSection.stringPublisher .sink{[unowned self] in self.rawString = $0; self.formattedString = encode($0) }.store(in: &objectBag) self.cell.encodeTextSection.stringPublisher .sink{[unowned self] in self.formattedString = $0; self.rawString = decode($0) }.store(in: &objectBag) self.cell.sourceTypePicker.itemPublisher .sink{[unowned self] in self.sourceType = $0 }.store(in: &objectBag) self.cell.fileDrop.urlsPublisher.compactMap{ $0.first } .sink{[unowned self] in self.formattedString = self.encodeFile($0) }.store(in: &objectBag) self.cell.exportButton.actionPublisher .sink{[unowned self] in self.exportFile() }.store(in: &objectBag) self.$sourceType .sink{[unowned self] in self.cell.sourceTypePicker.selectedItem = $0; updateEncodeView($0) }.store(in: &objectBag) self.$rawString .sink{[unowned self] in self.cell.inputTextSection.string = $0 }.store(in: &objectBag) self.$formattedString .sink{[unowned self] in self.cell.encodeTextSection.string = $0 }.store(in: &objectBag) } private func updateEncodeView(_ sourceType: SourceType) { switch sourceType { case .file: self.cell.inputSectionContainer.contentView = cell.fileDropSection case .text: self.cell.inputSectionContainer.contentView = cell.inputTextSection self.formattedString = self.encode(rawString) } } private func exportFile() { let panel = NSSavePanel() guard let data = Data(base64Encoded: formattedString), panel.runModal() == .OK, let url = panel.url else { return } do { try data.write(to: url) } catch { NSSound.beep() } } private func encodeFile(_ url: URL) -> String { (try? Data(contentsOf: url).base64EncodedString()) ?? "[Encode Failed]" } private func encode(_ string: String) -> String { string.data(using: .utf8)!.base64EncodedString() } private func decode(_ string: String) -> String { String(data: Data(base64Encoded: string) ?? Data(), encoding: .utf8) ?? "Not String" } } private enum SourceType: String, TextItem { case text = "Text Source" case file = "File Source" var title: String { rawValue.localized() } } final private class Base64DecoderView: Page { let sourceTypePicker = EnumPopupButton() let fileDrop = FileDrop() let exportButton = SectionButton(title: "Export".localized(), image: R.Image.export) let inputSectionContainer = NSPlaceholderView() let inputTextSection = TextViewSection(title: "Text".localized(), options: [.all]) lazy var fileDropSection = Section(title: "File".localized(), items: [fileDrop], toolbarItems: [exportButton]) let encodeTextSection = TextViewSection(title: "Encoded".localized(), options: [.all]) override func layout() { super.layout() let halfHeight = max(200, (self.frame.height - 190) / 2) self.fileDropSection.snp.remakeConstraints{ make in make.height.equalTo(halfHeight) } self.inputTextSection.snp.remakeConstraints{ make in make.height.equalTo(halfHeight) } self.encodeTextSection.snp.remakeConstraints{ make in make.height.equalTo(halfHeight) } } override func onAwake() { self.addSection(Section(title: "Configuration".localized(), items: [ Area(icon: R.Image.convert, title: "Source Type".localized(), control: sourceTypePicker) ])) self.inputSectionContainer.contentView = inputTextSection self.addSection(inputSectionContainer) self.addSection(inputTextSection) self.addSection(encodeTextSection) } } private let defaultRawString = "An Open-Source Swiss Army knife for developers. DevToys helps in everyday tasks like formatting JSON, comparing text, testing RegExp. No need to use many untruthful websites to do simple tasks with your data. With Smart Detection, DevToys is able to detect the best tool that can treat the data you copied in the clipboard of your Windows. Compact overlay lets you keep the app in small and on top of other windows. Multiple instances of the app can be used at once." private let defaultBase64String = "QW4gT3Blbi1Tb3VyY2UgU3dpc3MgQXJteSBrbmlmZSBmb3IgZGV2ZWxvcGVycy4KRGV2VG95cyBoZWxwcyBpbiBldmVyeWRheSB0YXNrcyBsaWtlIGZvcm1hdHRpbmcgSlNPTiwgY29tcGFyaW5nIHRleHQsIHRlc3RpbmcgUmVnRXhwLiBObyBuZWVkIHRvIHVzZSBtYW55IHVudHJ1dGhmdWwgd2Vic2l0ZXMgdG8gZG8gc2ltcGxlIHRhc2tzIHdpdGggeW91ciBkYXRhLiBXaXRoIFNtYXJ0IERldGVjdGlvbiwgRGV2VG95cyBpcyBhYmxlIHRvIGRldGVjdCB0aGUgYmVzdCB0b29sIHRoYXQgY2FuIHRyZWF0IHRoZSBkYXRhIHlvdSBjb3BpZWQgaW4gdGhlIGNsaXBib2FyZCBvZiB5b3VyIFdpbmRvd3MuIENvbXBhY3Qgb3ZlcmxheSBsZXRzIHlvdSBrZWVwIHRoZSBhcHAgaW4gc21hbGwgYW5kIG9uIHRvcCBvZiBvdGhlciB3aW5kb3dzLiBNdWx0aXBsZSBpbnN0YW5jZXMgb2YgdGhlIGFwcCBjYW4gYmUgdXNlZCBhdCBvbmNlLg==" ================================================ FILE: DevToys/DevToys/Body/Coder/HTMLDecoderView+.swift ================================================ // // HTMLDecoder.swift // DevToys // // Created by yuki on 2022/01/30. // import CoreUtil import HTMLEntities final class HTMLDecoderViewController: NSViewController { private let cell = HTMLDecoderView() @RestorableState("html.rawString") var rawString = "" @RestorableState("html.formattedString") var formattedString = "<script>alert("abc")</script>" override func loadView() { self.view = cell } override func viewDidLoad() { self.cell.decodeTextSection.stringPublisher .sink{[unowned self] in self.rawString = $0; self.formattedString = $0.htmlEscape() }.store(in: &objectBag) self.cell.encodeTextSection.stringPublisher .sink{[unowned self] in self.formattedString = $0; self.rawString = $0.htmlUnescape() }.store(in: &objectBag) self.$rawString .sink{[unowned self] in self.cell.decodeTextSection.string = $0 }.store(in: &objectBag) self.$formattedString .sink{[unowned self] in self.cell.encodeTextSection.string = $0 }.store(in: &objectBag) } } final private class HTMLDecoderView: Page { let decodeTextSection = CodeViewSection(title: "Decoded".localized(), options: [.all], language: .xml) let encodeTextSection = TextViewSection(title: "Encoded".localized(), options: [.all]) override func layout() { super.layout() let halfHeight = max(200, (self.frame.height - 100) / 2) self.decodeTextSection.snp.remakeConstraints{ make in make.height.equalTo(halfHeight) } self.encodeTextSection.snp.remakeConstraints{ make in make.height.equalTo(halfHeight) } } override func onAwake() { self.addSection(decodeTextSection) self.addSection(encodeTextSection) } } ================================================ FILE: DevToys/DevToys/Body/Coder/JWTDecoderView+.swift ================================================ // // JWTDecoder.swift // DevToys // // Created by yuki on 2022/02/01. // import CoreUtil final class JWTDecoderViewController: NSViewController { private let cell = JWTDecoderView() @RestorableState("jwt.token") var token = defaultToken @RestorableState("jwt.header") var header = defaultHeader @RestorableState("jwt.payload") var payload = defaultPayload override func loadView() { self.view = cell } override func viewDidLoad() { self.$token .sink{[unowned self] in self.cell.tokenTextSection.string = $0 }.store(in: &objectBag) self.$header .sink{[unowned self] in self.cell.headerCodeSection.string = $0 }.store(in: &objectBag) self.$payload .sink{[unowned self] in self.cell.payloadCodeSection.string = $0 }.store(in: &objectBag) self.cell.tokenTextSection.stringPublisher .sink{[unowned self] in self.token = $0 guard let (header, payload) = self.decodeJWT($0) else { self.header = "[Decode Failed]" self.payload = "[Decode Failed]" return } self.header = header self.payload = payload } .store(in: &objectBag) } private func decodeJWT(_ token: String) -> (header: String, payload: String)? { let components = token.split(separator: ".").map{ String($0) } guard components.count == 3 else { print("not3"); return nil } guard let header = self.decodeBase64ToJson(components[0]) else { print("he"); return nil } guard let payload = self.decodeBase64ToJson(components[1]) else { print("pa"); return nil } return (header, payload) } private func decodeBase64ToJson(_ string: String) -> String? { guard let data = Data(base64Encoded: string) ?? Data(base64Encoded: string + "=") ?? Data(base64Encoded: string + "==") else { return nil } guard let object = try? JSONSerialization.jsonObject(with: data, options: []) else { return nil } guard let json = try? JSONSerialization.data(withJSONObject: object, options: .prettyPrinted) else { return nil } return String(data: json, encoding: .utf8) } } final private class JWTDecoderView: Page { let tokenTextSection = TextViewSection(title: "JWT Token".localized(), options: .defaultInput) let headerCodeSection = CodeViewSection(title: "Header".localized(), options: .defaultOutput, language: .javascript) let payloadCodeSection = CodeViewSection(title: "Payload".localized(), options: .defaultOutput, language: .javascript) override func layout() { super.layout() let contentHeight = max(100, (self.frame.height - 100) / 3) self.tokenTextSection.snp.remakeConstraints{ make in make.height.equalTo(contentHeight) } self.headerCodeSection.snp.remakeConstraints{ make in make.height.equalTo(contentHeight) } self.payloadCodeSection.snp.remakeConstraints{ make in make.height.equalTo(contentHeight) } } override func onAwake() { self.addSection(tokenTextSection) self.tokenTextSection.textView.snp.remakeConstraints{ make in make.height.equalTo(85) } self.addSection(headerCodeSection) self.headerCodeSection.textView.snp.remakeConstraints{ make in make.height.equalTo(100) } self.addSection(payloadCodeSection) self.payloadCodeSection.textView.snp.remakeConstraints{ make in make.height.equalTo(100) } } } private let defaultToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiYWdlIjoxNn0.Ir_wyzMjqDXeeaGWJVgdysutJ6C9E3MX11t38LD2K60" private let defaultPayload = """ { "sub": "1234567890", "name": "Alice", "age": 16 } """ private let defaultHeader = """ { "alg": "HS256", "typ": "JWT" } """ ================================================ FILE: DevToys/DevToys/Body/Coder/URLDecoderView+.swift ================================================ // // URLDecoder.swift // DevToys // // Created by yuki on 2022/01/30. // import CoreUtil final class URLDecoderViewController: NSViewController { private let cell = URLDecoderView() @RestorableState("url.rawString") var rawString = "https://www.microsoft.com/ja-jp/p/devtoys/9pgcv4v3bk4w?rtc=1#activetab=pivot:overviewtab" @RestorableState("url.formattedString") var formattedString = "https%3A%2F%2Fwww.microsoft.com%2Fja-jp%2Fp%2Fdevtoys%2F9pgcv4v3bk4w%3Frtc%3D1%23activetab%3Dpivot%3Aoverviewtab" override func loadView() { self.view = cell } override func viewDidLoad() { self.cell.decodeTextSection.stringPublisher .sink{[unowned self] in self.rawString = $0; self.formattedString = encodeToURL($0) }.store(in: &objectBag) self.cell.encodeTextSection.stringPublisher .sink{[unowned self] in self.formattedString = $0; if let s = $0.removingPercentEncoding { self.rawString = s } }.store(in: &objectBag) self.$rawString .sink{[unowned self] in self.cell.decodeTextSection.string = $0 }.store(in: &objectBag) self.$formattedString .sink{[unowned self] in self.cell.encodeTextSection.string = $0 }.store(in: &objectBag) } private func encodeToURL(_ string: String) -> String { let allowedCharacters = NSCharacterSet.alphanumerics.union(.init(charactersIn: "-._~")) return string.addingPercentEncoding(withAllowedCharacters: allowedCharacters) ?? "???" } } final private class URLDecoderView: Page { let decodeTextSection = TextViewSection(title: "Decoded".localized(), options: [.all]) let encodeTextSection = TextViewSection(title: "Encoded".localized(), options: [.all]) override func layout() { super.layout() let halfHeight = max(200, (self.frame.height - 100) / 2) self.decodeTextSection.snp.remakeConstraints{ make in make.height.equalTo(halfHeight) } self.encodeTextSection.snp.remakeConstraints{ make in make.height.equalTo(halfHeight) } } override func onAwake() { self.addSection(decodeTextSection) self.addSection(encodeTextSection) } } ================================================ FILE: DevToys/DevToys/Body/Convert/DateConverterView+.swift ================================================ // // DateConverter.swift // DevToys // // Created by yuki on 2022/02/04. // import CoreUtil final class DateConverterViewController: NSViewController { private let cell = DateConverterView() @Observable var date = Date() private let isoFormatter = ISO8601DateFormatter() private let gmtFormatter = DateFormatter() => { $0.locale = Locale(identifier: "en_US_POSIX") $0.timeZone = TimeZone(abbreviation: "GMT") } override func loadView() { self.view = cell } override func viewDidLoad() { self.$date .sink{[unowned self] in self.cell.datePicker.date = $0 self.cell.unixTimeField.value = $0.timeIntervalSince1970.rounded() self.cell.isoDateField.string = isoFormatter.string(from: $0) self.cell.graphicDatePicker.dateValue = $0 } .store(in: &objectBag) self.cell.graphicDatePicker.actionPublisher .sink{[unowned self] in self.date = cell.graphicDatePicker.dateValue }.store(in: &objectBag) self.cell.nowButton.actionPublisher .sink{[unowned self] in self.date = Date() }.store(in: &objectBag) self.cell.datePicker.datePublisher .sink{[unowned self] in self.date = $0 }.store(in: &objectBag) self.cell.unixTimeField.valuePublisher .sink{[unowned self] in self.date = Date(timeIntervalSince1970: $0.reduce(self.date.timeIntervalSince1970)) }.store(in: &objectBag) self.cell.isoDateField.changeStringPublisher.compactMap{[unowned self] in isoFormatter.date(from: $0) } .sink{[unowned self] in self.date = $0 }.store(in: &objectBag) } } final private class DateConverterView: Page { let datePicker = DatePicker() let utcDatePicker = DatePicker() let nowButton = Button(title: "Now") let unixTimeField = NumberField() let isoDateField = TextField(showCopyButton: false) let timestampDateField = TextField(showCopyButton: false) let graphicDatePicker = NSDatePicker() override func onAwake() { self.addSection(Section(title: "Date".localized(), items: [ NSStackView() => { $0.distribution = .equalSpacing $0.addArrangedSubview(datePicker) $0.addArrangedSubview(nowButton) } ])) self.datePicker.snp.remakeConstraints{ make in make.right.equalTo(nowButton.snp.left).inset(-8) } self.addSection(Section(title: "Unix Time".localized(), items: [unixTimeField])) self.unixTimeField.showStepper = false self.unixTimeField.snp.remakeConstraints{ make in make.height.equalTo(R.Size.controlHeight) } self.addSection(Section(title: "ISO 8601".localized(), items: [isoDateField])) self.addSection(Section(title: "Timestamp".localized(), items: [timestampDateField])) self.addSection(Section(title: "Calender".localized(), items: [graphicDatePicker])) self.graphicDatePicker.datePickerStyle = .clockAndCalendar } } ================================================ FILE: DevToys/DevToys/Body/Convert/JSONYamlConverterView+.swift ================================================ // // JSONYamlConverter+.swift // DevToys // // Created by yuki on 2022/01/30. // import CoreUtil import Yams import SwiftJSONFormatter final class JSONYamlConverterViewController: NSViewController { private let cell = JSONYamlConverterView() @RestorableState("jy.format") private var formatStyle: FormatStyle = .pretty @RestorableState("jy.json") private var jsonCode = defaultJsonCode @RestorableState("jy.yaml") private var yamlCode = defaultYamlCode override func loadView() { self.view = cell } override func viewDidLoad() { self.$jsonCode .sink{[unowned self] in self.cell.jsonSection.string = $0 }.store(in: &objectBag) self.$yamlCode .sink{[unowned self] in self.cell.yamlSection.string = $0 }.store(in: &objectBag) self.$formatStyle .sink{[unowned self] in self.cell.formatStylePicker.selectedItem = $0 }.store(in: &objectBag) self.cell.formatStylePicker.itemPublisher .sink{[unowned self] in self.formatStyle = $0 }.store(in: &objectBag) self.cell.jsonSection.stringPublisher .sink{[unowned self] in self.jsonCode = $0 if let yamlCode = convertJsonToYaml(code: $0, formatStyle: self.formatStyle) { self.yamlCode = yamlCode } } .store(in: &objectBag) self.cell.yamlSection.stringPublisher .sink{[unowned self] in self.yamlCode = $0 if let jsonCode = convertYamlToJson(code: $0, formatStyle: self.formatStyle) { self.jsonCode = jsonCode } } .store(in: &objectBag) } private func convertYamlToJson(code: String, formatStyle: FormatStyle) -> String? { if code.isEmpty { return "" } guard let object = try? Yams.load(yaml: code) else { return nil } var options = JSONSerialization.WritingOptions() options.insert(.sortedKeys) if formatStyle == .pretty { options.insert(.prettyPrinted) } return try? objc_try { guard let data = try? JSONSerialization.data(withJSONObject: object, options: options) else { return nil } let json = String(data: data, encoding: .utf8)! return SwiftJSONFormatter.beautify(json, indent: " ") } } private func convertJsonToYaml(code: String, formatStyle: FormatStyle) -> String? { if code.isEmpty { return "" } return objc_try({ guard let object = try? JSONSerialization.jsonObject(with: code.data(using: .utf8)!) else { return nil } let swiftObject = convertNSObject(object) return try? Yams.dump(object: swiftObject) }, catch: {_ in return nil }) } private func convertNSObject(_ object: Any) -> Any { if let object = object as? NSDictionary { return (object as! [String: Any]).mapValues{ convertNSObject($0) } } if let object = object as? NSArray { return (object as! [Any]).map{ convertNSObject($0) } } if let object = object as? NSString { return object as String } if let object = object as? NSNumber { switch CFNumberGetType(object) { case .cgFloatType, .floatType, .doubleType, .float32Type, .float64Type: return object.doubleValue case .longLongType, .longType, .shortType, .intType, .sInt8Type, .sInt16Type, .sInt32Type, .sInt64Type, .cfIndexType, .nsIntegerType: return object.intValue case .charType: return object.boolValue @unknown default: return object.intValue } } return object } } private enum FormatStyle: String, TextItem { case pretty = "Pretty" case minified = "Minified" var title: String { rawValue.localized() } } final private class JSONYamlConverterView: Page { let formatStylePicker = EnumPopupButton() let jsonSection = CodeViewSection(title: "JSON", options: .all, language: .javascript) let yamlSection = CodeViewSection(title: "Yaml", options: .all, language: .yaml) private lazy var ioStack = self.addSection2(jsonSection, yamlSection) override func layout() { super.layout() self.ioStack.snp.remakeConstraints{ make in make.height.equalTo(max(240, self.frame.height - 170)) } } override func onAwake() { self.addSection(Section(title: "Configuration".localized(), items: [ Area(icon: R.Image.format, title: "Format".localized(), control: formatStylePicker) ])) } } private let defaultJsonCode = """ { "type" : "members", "members" : [ { "name" : "Alice", "age" : 16 }, { "name" : "Bob", "age" : 24 } ], } """ private let defaultYamlCode = """ type: members members: - age: 16 name: Alice - age: 24 name: Bob """ ================================================ FILE: DevToys/DevToys/Body/Convert/NumberBaseConverterView+.swift ================================================ // // NumberBaseConverter.swift // DevToys // // Created by yuki on 2022/01/30. // import CoreUtil final class NumberBaseConverterViewController: NSViewController { private let cell = NumberBaseConverterView() @RestorableState("numbase.format") private var formatNumber = true @RestorableState("numbase.2") private var value2 = "1 0101 1010 0101 0011" @RestorableState("numbase.10") private var value10 = "88,659" @RestorableState("numbase.8") private var value8 = "255 123" @RestorableState("numbase.16") private var value16 = "1 5A53" override func loadView() { self.view = cell } override func viewDidLoad() { self.$value2 .sink{[unowned self] in cell.binarySection.string = $0 }.store(in: &objectBag) self.$value8 .sink{[unowned self] in cell.octalSection.string = $0 }.store(in: &objectBag) self.$value10 .sink{[unowned self] in cell.decimalSection.string = $0 }.store(in: &objectBag) self.$value16 .sink{[unowned self] in cell.hexSection.string = $0 }.store(in: &objectBag) self.$formatNumber .sink{[unowned self] in cell.formatSwitch.state = $0 ? .on : .off }.store(in: &objectBag) self.cell.binarySection.stringPublisher .sink{[unowned self] in self.updateValue(string: $0, inputRadix: 2) }.store(in: &objectBag) self.cell.octalSection.stringPublisher .sink{[unowned self] in self.updateValue(string: $0, inputRadix: 8) }.store(in: &objectBag) self.cell.decimalSection.stringPublisher .sink{[unowned self] in self.updateValue(string: $0, inputRadix: 10) }.store(in: &objectBag) self.cell.hexSection.stringPublisher .sink{[unowned self] in self.updateValue(string: $0, inputRadix: 16) }.store(in: &objectBag) self.cell.formatSwitch.actionPublisher.map{[unowned self] in self.cell.formatSwitch.state == .on } .sink{[unowned self] in self.formatNumber = $0; updateFormat() }.store(in: &objectBag) } private func updateFormat() { self.updateValue(string: value10, inputRadix: 10) } private func updateValue(string: String, inputRadix: Int) { let value = Int(string.replacingOccurrences(of: " ", with: "").replacingOccurrences(of: ",", with: ""), radix: inputRadix) self.value2 = self.convertBinary(value, format: self.formatNumber) self.value8 = self.convertOctal(value, format: self.formatNumber) self.value10 = self.convertDecimal(value, format: self.formatNumber) self.value16 = self.convertHex(value, format: self.formatNumber) } private func convertHex(_ value: Int?, format: Bool) -> String { guard let value = value else { return "0" } let string = String(value, radix: 16, uppercase: true) if format { return splitString(string, split: 4, separator: " ") } else { return string } } private func convertDecimal(_ value: Int?, format: Bool) -> String { guard let value = value else { return "0" } let string = String(value, radix: 10, uppercase: true) if format { return splitString(string, split: 3, separator: ",") } else { return string } } private func convertOctal(_ value: Int?, format: Bool) -> String { guard let value = value else { return "0" } let string = String(value, radix: 8, uppercase: true) if format { return splitString(string, split: 3, separator: " ") } else { return string } } private func convertBinary(_ value: Int?, format: Bool) -> String { guard let value = value else { return "0" } let string = String(value, radix: 2, uppercase: true) if format { return splitString(string, split: 4, separator: " ") } else { return string } } private func splitString(_ string: String, split: Int, separator: String) -> String { var result = "" var count = string.count for c in string { count -= 1 result.append(c) if count != 0, count % split == 0 { result.append(separator) } } return result } private func convert(_ value: Int?, radix: Int) -> String { guard let value = value else { return "0" } return String(value, radix: radix, uppercase: true) } } final private class NumberBaseConverterView: Page { let formatSwitch = NSSwitch() let decimalSection = TextFieldSection(title: "Decimal".localized()) let hexSection = TextFieldSection(title: "Hexdecimal".localized()) let octalSection = TextFieldSection(title: "Octal".localized()) let binarySection = TextFieldSection(title: "Binary".localized()) private lazy var formatNumberArea = Area(icon: R.Image.format, title: "Format Number".localized(), control: formatSwitch) private lazy var configurationSection = Section(title: "Configuration".localized(), items: [formatNumberArea]) override func onAwake() { self.addSection(configurationSection) self.addSection(decimalSection) self.addSection(hexSection) self.addSection(octalSection) self.addSection(binarySection) } } ================================================ FILE: DevToys/DevToys/Body/Format/JSONFormatterView+.swift ================================================ // // JSONFormatter+.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil import SwiftJSONFormatter final class JSONFormatterViewController: NSViewController { @RestorableState("json.spacingtype") var spacingType: JSONSpacingType = .spaces4 @RestorableState("jq.rawCode") var rawCode: String = #"{ "Hello": "World" }"# @RestorableState("jq.formattedCode") var formattedCode: String = "{\n \"Hello\": \"World\"\n}" private let cell = JSONFormatterView() override func loadView() { self.view = cell } override func viewDidLoad() { self.$spacingType .sink{[unowned self] in cell.indentControl.selectedItem = $0 }.store(in: &objectBag) self.$rawCode .sink{[unowned self] in cell.inputSection.string = $0 }.store(in: &objectBag) self.$formattedCode .sink{[unowned self] in cell.outputSection.string = $0 }.store(in: &objectBag) self.cell.inputSection.stringPublisher .sink{[unowned self] in self.rawCode = $0; updateFormattedCode() }.store(in: &objectBag) self.cell.indentControl.itemPublisher .sink{[unowned self] in self.spacingType = $0; updateFormattedCode() }.store(in: &objectBag) } private func updateFormattedCode() { self.formattedCode = processJSON(rawCode, spacingType: spacingType) } private func processJSON(_ code: String, spacingType: JSONSpacingType) -> String { switch spacingType { case .spaces2: return SwiftJSONFormatter.beautify(code, indent: " ") case .spaces4: return SwiftJSONFormatter.beautify(code, indent: " ") case .tab1: return SwiftJSONFormatter.beautify(code, indent: "\t") case .minified: return SwiftJSONFormatter.minify(code) } } } enum JSONSpacingType: String, TextItem { case spaces2 = "2 Spaces" case spaces4 = "4 Spaces" case tab1 = "1 Tab" case minified = "Minified" var title: String { rawValue.localized() } } final private class JSONFormatterView: Page { let indentControl = EnumPopupButton() let inputSection = CodeViewSection(title: "Input".localized(), options: .defaultInput, language: .javascript) let outputSection = CodeViewSection(title: "Output".localized(), options: .defaultOutput, language: .javascript) private lazy var indentArea = Area(icon: R.Image.spacing, title: "Indentation".localized(), control: indentControl) private lazy var configurationSection = Section(title: "Configuration".localized(), items: [indentArea]) private lazy var ioStack = self.addSection2(inputSection, outputSection) override func layout() { super.layout() self.ioStack.snp.remakeConstraints{ make in make.height.equalTo(max(240, self.frame.height - 180)) } } override func onAwake() { self.addSection(configurationSection) self.outputSection.textView.textView.usesFindBar = true } } ================================================ FILE: DevToys/DevToys/Body/Format/SQLFormatterView+.swift ================================================ // // SQLFormatterView+.swift // DevToys // // Created by yuki on 2022/02/26. // import CoreUtil final class SQLFormatterViewController: NSViewController { private let cell = SQLFormatterView() override func loadView() { self.view = cell } override func viewDidLoad() { } } final private class SQLFormatterView: Page { override func onAwake() { } } ================================================ FILE: DevToys/DevToys/Body/Format/XMLFormatterView+.swift ================================================ // // XMLFormatter.swift // DevToys // // Created by yuki on 2022/02/04. // import CoreUtil final class XMLFormatterViewController: NSViewController { @RestorableState("xml.rawCode") private var rawCode: String = "" @RestorableState("xml.formattedCode") private var formattedCode: String = "" @RestorableState("xml.documentType") private var documentType: DocumentType = .xmlDocument @RestorableState("xml.pretty") private var pretty = true @RestorableState("xml.autofix") private var autofix = true private let cell = XMLFormatterView() override func loadView() { self.view = cell } override func viewDidLoad() { self.$rawCode .sink{[unowned self] in cell.inputSection.string = $0 }.store(in: &objectBag) self.$formattedCode .sink{[unowned self] in cell.outputSection.string = $0 }.store(in: &objectBag) self.$documentType .sink{[unowned self] in cell.documentTypeControl.selectedItem = $0 }.store(in: &objectBag) self.$pretty .sink{[unowned self] in cell.prettySwitch.isOn = $0 }.store(in: &objectBag) self.$autofix .sink{[unowned self] in cell.autoFixSwitch.isOn = $0 }.store(in: &objectBag) self.cell.prettySwitch.isOnPublisher .sink{[unowned self] in self.pretty = $0; updateFormattedCode() }.store(in: &objectBag) self.cell.autoFixSwitch.isOnPublisher .sink{[unowned self] in self.autofix = $0; updateFormattedCode() }.store(in: &objectBag) self.cell.documentTypeControl.itemPublisher .sink{[unowned self] in self.documentType = $0; updateFormattedCode() }.store(in: &objectBag) self.cell.inputSection.stringPublisher .sink{[unowned self] in self.rawCode = $0; updateFormattedCode() }.store(in: &objectBag) self.updateFormattedCode() } private func updateFormattedCode() { do { var options = XMLNode.Options() if pretty { options.insert(.nodePrettyPrint) } if autofix { switch documentType { case .htmlDocument: options.insert(.documentTidyHTML) case .xmlDocument: options.insert(.documentTidyXML) } } let document = try XMLDocument(xmlString: rawCode, options: options) self.formattedCode = document.xmlString(options: options) .replacingOccurrences(of: #""#, with: "") .replacingOccurrences(of: #""#, with: "") .replacingOccurrences(of: #""#, with: "") .replacingOccurrences(of: #""#, with: "") .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) } catch { self.formattedCode = "[Invalid Document]" } } } private enum DocumentType: String, TextItem { case htmlDocument = "HTML Document" case xmlDocument = "XML Document" var title: String { rawValue.localized() } } final private class XMLFormatterView: Page { let prettySwitch = NSSwitch() let autoFixSwitch = NSSwitch() let documentTypeControl = EnumPopupButton() let inputSection = CodeViewSection(title: "Input".localized(), options: .defaultInput, language: .xml) let outputSection = CodeViewSection(title: "Output".localized(), options: .defaultOutput, language: .xml) private lazy var configurationSection = Section(title: "Configuration".localized(), items: [ Area(icon: R.Image.convert, title: "Document Type".localized(), control: documentTypeControl), NSStackView() => { $0.orientation = .horizontal $0.distribution = .fillEqually $0.addArrangedSubview(Area(icon: R.Image.format, title: "Auto Fix Document".localized(), control: autoFixSwitch)) $0.addArrangedSubview(Area(icon: R.Image.format, title: "Pretty Document".localized(), control: prettySwitch)) } ]) private lazy var ioStack = self.addSection2(inputSection, outputSection) override func layout() { super.layout() self.ioStack.snp.remakeConstraints{ make in make.height.equalTo(max(240, self.frame.height - 230)) } } override func onAwake() { self.addSection(configurationSection) } } ================================================ FILE: DevToys/DevToys/Body/Generator/ChecksumGeneratorView+.swift ================================================ // // ChecksumGenerator.swift // DevToys // // Created by yuki on 2022/02/04. // import CoreUtil final class ChecksumGeneratorViewController: NSViewController { @RestorableState("checksum.uppercase") private var isUppercase = false @RestorableState("checksum.alg") private var hashAlgorithm = HashAlgorithm.md5 @RestorableState("checksum.comparer") private var comparer = "" @Observable private var output = "" @Observable var isError = true @Observable var fileURL: URL? private let cell = ChecksumGeneratorView() override func loadView() { self.view = cell } override func viewDidLoad() { self.$isUppercase .sink{[unowned self] in cell.formatSwitch.isOn = $0 }.store(in: &objectBag) self.$hashAlgorithm .sink{[unowned self] in cell.hashAlgorithmPicker.selectedItem = $0 }.store(in: &objectBag) self.$output .sink{[unowned self] in cell.outputFieldSection.string = $0 }.store(in: &objectBag) self.$comparer .sink{[unowned self] in cell.compareFieldSection.string = $0 }.store(in: &objectBag) self.$isError .sink{[unowned self] in cell.compareFieldSection.textField.isError = $0 }.store(in: &objectBag) self.cell.formatSwitch.isOnPublisher .sink{[unowned self] in self.isUppercase = $0; updateHash() }.store(in: &objectBag) self.cell.hashAlgorithmPicker.itemPublisher .sink{[unowned self] in self.hashAlgorithm = $0; updateHash() }.store(in: &objectBag) self.cell.compareFieldSection.stringPublisher .sink{[unowned self] in self.comparer = $0; updateComparer() }.store(in: &objectBag) self.cell.fileSection.urlsPublisher.compactMap{ $0.first } .sink{[unowned self] in fileURL = $0; self.updateHash() }.store(in: &objectBag) } private func updateHash() { guard let url = self.fileURL, let data = try? Data(contentsOf: url) else { return NSSound.beep() } switch hashAlgorithm { case .md5: self.output = data.md5().hexString(uppercase: isUppercase) case .sha1: self.output = data.sha1().hexString(uppercase: isUppercase) case .sha256: self.output = data.sha256().hexString(uppercase: isUppercase) case .sha384: self.output = data.sha384().hexString(uppercase: isUppercase) case .sha512: self.output = data.sha512().hexString(uppercase: isUppercase) } self.updateComparer() } private func updateComparer() { self.isError = comparer.lowercased() != output.lowercased() } } extension Data { func hexString(uppercase: Bool) -> String { self.map{ String(format: uppercase ? "%02X" : "%02x", $0) }.joined() } } private enum HashAlgorithm: String, TextItem { case md5 = "MD5" case sha1 = "SHA1" case sha256 = "SHA256" case sha384 = "SHA384" case sha512 = "SHA512" var title: String { rawValue } } final private class ChecksumGeneratorView: Page { let formatSwitch = NSSwitch() let hashAlgorithmPicker = EnumPopupButton() let fileSection = FileDropSection() let outputFieldSection = TextFieldSection(title: "Output".localized(), isEditable: false) let compareFieldSection = TextFieldSection(title: "Output Comparer".localized(), isEditable: true) override func onAwake() { self.addSection(Section(title: "Configuration".localized(), items: [ Area(icon: R.Image.format, title: "Uppercase".localized(), control: formatSwitch), Area(icon: R.Image.convert, title: "Hash Algorithm".localized(), message: "Select which algorithm you want to use".localized(), control: hashAlgorithmPicker) ])) self.addSection(fileSection) self.addSection(outputFieldSection) self.addSection(compareFieldSection) } } ================================================ FILE: DevToys/DevToys/Body/Generator/HashGeneratorView+.swift ================================================ // // HashGenerator.swift // DevToys // // Created by yuki on 2022/02/01. // import CoreUtil import CryptoSwift final class HashGeneratorViewController: NSViewController { private let cell = HashGeneratorView() @RestorableState("hash.upper") var isUppercase = false @RestorableState("hash.input") var input = "Hello World" @RestorableState("hash.md5") var md5 = "" @RestorableState("hash.sha1") var sha1 = "" @RestorableState("hash.sha256") var sha256 = "" @RestorableState("hash.sha512") var sha512 = "" override func loadView() { self.view = cell } override func viewDidLoad() { self.updateHash() self.$isUppercase.sink{[unowned self] in self.cell.formatSwitch.isOn = $0 }.store(in: &objectBag) self.$input.sink{[unowned self] in self.cell.textInputSection.string = $0 }.store(in: &objectBag) self.$md5.sink{[unowned self] in self.cell.md5Section.string = $0 }.store(in: &objectBag) self.$sha1.sink{[unowned self] in self.cell.sha1Section.string = $0 }.store(in: &objectBag) self.$sha256.sink{[unowned self] in self.cell.sha256Section.string = $0 }.store(in: &objectBag) self.$sha512.sink{[unowned self] in self.cell.sha512Section.string = $0 }.store(in: &objectBag) self.cell.textInputSection.stringPublisher .sink{[unowned self] in self.input = $0; updateHash() }.store(in: &objectBag) self.cell.formatSwitch.isOnPublisher .sink{[unowned self] in self.isUppercase = $0; updateHash() }.store(in: &objectBag) } private func updateHash() { self.md5 = isUppercase ? input.md5().uppercased() : input.md5() self.sha1 = isUppercase ? input.sha1().uppercased() : input.sha1() self.sha256 = isUppercase ? input.sha256().uppercased() : input.sha256() self.sha512 = isUppercase ? input.sha512().uppercased() : input.sha512() } } final class HashGeneratorView: Page { let formatSwitch = NSSwitch() let textInputSection = TextViewSection(title: "Input".localized(), options: .all) let md5Section = TextFieldSection(title: "MD5", isEditable: false) let sha1Section = TextFieldSection(title: "SHA1", isEditable: false) let sha256Section = TextFieldSection(title: "SHA256", isEditable: false) let sha512Section = TextFieldSection(title: "SHA512", isEditable: false) private lazy var formatNumberArea = Area(icon: R.Image.format, title: "Uppercase".localized(), control: formatSwitch) private lazy var configurationSection = Section(title: "Configuration".localized(), items: [formatNumberArea]) override func onAwake() { self.addSection(configurationSection) self.addSection(textInputSection) self.textInputSection.snp.remakeConstraints{ make in make.height.equalTo(180) } self.addSection(md5Section) self.addSection(sha1Section) self.addSection(sha256Section) self.addSection(sha512Section) } } extension NSSwitch { var isOn: Bool { get { self.state == .on } set { self.state = newValue ? .on : .off } } var isOnPublisher: AnyPublisher { self.actionPublisher.map{_ in self.state == .on }.eraseToAnyPublisher() } } ================================================ FILE: DevToys/DevToys/Body/Generator/LoremIpsumGeneratorView+.swift ================================================ // // LoremIpsumGenerator.swift // DevToys // // Created by yuki on 2022/02/01. // import CoreUtil final class LoremIpsumGeneratorViewController: NSViewController { private let cell = LoremIpsumGeneratorView() @RestorableState("li.type") var generateType = LoremIpsumGenerateType.sentences @RestorableState("li.length") var length = 3 @RestorableState("li.output") var output = "hello world" override func loadView() { self.view = cell } override func viewDidLoad() { self.generate() self.$generateType.sink{[unowned self] in self.cell.typePicker.selectedItem = $0 }.store(in: &objectBag) self.$length.sink{[unowned self] in self.cell.lengthField.value = Double($0) }.store(in: &objectBag) self.$output.sink{[unowned self] in self.cell.outputSection.string = $0 }.store(in: &objectBag) self.cell.typePicker.itemPublisher .sink{[unowned self] in self.generateType = $0; generate() }.store(in: &objectBag) self.cell.lengthField.valuePublisher.map{ $0.map{ Int($0) } } .sink{[unowned self] in let next = $0.reduce(self.length).clamped(1...) if next != self.length { self.length = next; self.generate() } }.store(in: &objectBag) } private func generate() { switch self.generateType { case .words: self.output = generateWords(length) case .sentences: self.output = generateSentences(length) case .paragraphs: self.output = generateParagraphs(length) } } private func generateParagraphs(_ count: Int) -> String { if count < 0 { return "" } return (0.. String { if count < 0 { return "" } return (0.. String { if count < 0 { return "" } return (0..() let lengthField = NumberField() let outputSection = TextViewSection(title: "Output".localized(), options: [.outputable, .copyable, .inputable]) override func layout() { super.layout() self.outputSection.snp.remakeConstraints{ make in make.height.equalTo(max(240, self.frame.height - 230)) } } override func onAwake() { self.addSection(Section(title: "Configuration".localized(), items: [ Area(icon: R.Image.text, title: "Type".localized(), message: "Type of generating Lorem Ipsum".localized(), control: typePicker), Area(icon: R.Image.number, title: "Length".localized(), message: "Length of generating Lorem Ipsum".localized(), control: lengthField), ])) self.addSection(outputSection) } } private let wordSource: Set = ["est", "quis", "ipsum", "labore", "cillum", "velit", "consequat", "dolore", "proident", "non", "sint", "nisi", "in", "officia", "sed", "deserunt", "aute", "pariatur", "aliquip", "eiusmod", "ex", "excepteur", "et", "esse", "sunt", "dolor", "nulla", "lorem", "ullamco", "amet", "culpa", "eu", "adipiscing", "commodo", "ea", "fugiat", "qui", "minim", "enim", "ut", "anim", "cupidatat", "aliqua", "laboris", "ad", "exercitation", "id", "mollit", "tempor", "veniam", "reprehenderit", "occaecat", "sit", "consectetur", "duis", "voluptate", "nostrud", "laborum", "magna", "incididunt", "elit", "irure", "do"] ================================================ FILE: DevToys/DevToys/Body/Generator/QRCodeGeneratorView+.swift ================================================ // // QLCodeGeneratorView+.swift // DevToys // // Created by yuki on 2022/02/23. // import CoreUtil import CoreImage final class QRCodeGeneratorViewController: NSViewController { private let cell = URLDecoderView() @RestorableState("qrcode.rawString") var rawString = "Hello World" @RestorableState("qrcode.correctionLevel") var correctionLevel: QRInputCorrectionLevel = .default @Observable var qrimage: NSImage? = nil override func loadView() { self.view = cell } override func viewDidLoad() { self.$rawString .sink{[unowned self] in self.cell.inputTextSection.string = $0 }.store(in: &objectBag) self.$qrimage .sink{[unowned self] in self.cell.imageView.image = $0 }.store(in: &objectBag) self.$correctionLevel .sink{[unowned self] in self.cell.correctionLevelPicker.selectedItem = $0 }.store(in: &objectBag) self.cell.inputTextSection.stringPublisher .sink{[unowned self] in self.rawString = $0; updateQRCode() }.store(in: &objectBag) self.cell.correctionLevelPicker.itemPublisher .sink{[unowned self] in self.correctionLevel = $0; updateQRCode() }.store(in: &objectBag) self.cell.exportButton.actionPublisher .sink{[unowned self] in self.exportImage() }.store(in: &objectBag) self.updateQRCode() } private func exportImage() { guard let qrimage = qrimage?.png else { return NSSound.beep() } let panel = NSSavePanel() panel.nameFieldStringValue = "QRCode.png" guard panel.runModal() == .OK, let url = panel.url else { return } do { try qrimage.write(to: url) } catch { assertionFailure("\(error)") } } private func updateQRCode() { self.qrimage = generateQRCode(from: rawString) } private func generateQRCode(from string: String) -> NSImage? { if string.isEmpty { return nil } let data = string.data(using: .utf8) guard let qrfilter = CIFilter(name: "CIQRCodeGenerator") else { return nil } qrfilter.setValue(data, forKey: "inputMessage") switch correctionLevel { case .high: qrfilter.setValue("H", forKey: "inputCorrectionLevel") case .default: qrfilter.setValue("Q", forKey: "inputCorrectionLevel") case .medium: qrfilter.setValue("M", forKey: "inputCorrectionLevel") case .low: qrfilter.setValue("L", forKey: "inputCorrectionLevel") } let transform = CGAffineTransform(scaleX: 3, y: 3) guard let output = qrfilter.outputImage?.transformed(by: transform) else { return nil } let rep = NSCIImageRep(ciImage: output) let nsImage = NSImage(size: rep.size) nsImage.addRepresentation(rep) return nsImage } } enum QRInputCorrectionLevel: String, TextItem { case high = "High" case `default` = "Default" case medium = "Medium" case low = "Low" var title: String { rawValue.localized() } } final private class URLDecoderView: Page { let correctionLevelPicker = EnumPopupButton() let inputTextSection = TextViewSection(title: "Input".localized(), options: .defaultInput) let imageView = DragImageView() let exportButton = SectionButton(title: "Export".localized(), image: R.Image.export) let imageViewDelegate = DragImageViewBlockDelegate{ "QRCode.png" } override func onAwake() { self.addSection(Section(title: "Configuration".localized(), items: [ Area(icon: R.Image.paramators, title: "Correction Level".localized(), control: correctionLevelPicker) ])) self.addSection(inputTextSection) self.inputTextSection.snp.makeConstraints{ make in make.height.equalTo(320) } self.imageView.delegate = imageViewDelegate self.addSection(Section(title: "QR Code".localized(), items: [ NSStackView() => { $0.alignment = .centerX $0.addArrangedSubview(imageView) } ], toolbarItems: [exportButton])) } } ================================================ FILE: DevToys/DevToys/Body/Generator/UUIDGeneratorView+.swift ================================================ // // UUIDGenerator.swift // DevToys // // Created by yuki on 2022/02/01. // import CoreUtil final class UUIDGeneratorViewController: NSViewController { private let cell = UUIDGeneratorView() @RestorableState("uuid.isHyphened") var isHyphened = true @RestorableState("uuid.uppercase") var isUppercase = true @RestorableState("uuid.count") var count = 3 @RestorableState("uuid.uuid") var uuids = "\(UUID().uuidString)\n" override func loadView() { self.view = cell } override func viewDidLoad() { self.$isHyphened .sink{[unowned self] in self.cell.hyphensSwitch.isOn = $0 }.store(in: &objectBag) self.$isUppercase .sink{[unowned self] in self.cell.uppercaseSwitch.isOn = $0 }.store(in: &objectBag) self.$count .sink{[unowned self] in self.cell.generateCount.value = Double($0) }.store(in: &objectBag) self.$uuids .sink{[unowned self] in self.cell.uuidView.string = $0 }.store(in: &objectBag) self.cell.hyphensSwitch.isOnPublisher .sink{[unowned self] in self.isHyphened = $0 }.store(in: &objectBag) self.cell.uppercaseSwitch.isOnPublisher .sink{[unowned self] in self.isUppercase = $0 }.store(in: &objectBag) self.cell.generateCount.valuePublisher .sink{[unowned self] in self.count = $0.map{ Int($0) }.reduce(self.count).clamped(1...) }.store(in: &objectBag) self.cell.generateButton.actionPublisher .sink{[unowned self] in self.generateUUID() }.store(in: &objectBag) self.cell.clearButton.actionPublisher .sink{[unowned self] in self.uuids = "" }.store(in: &objectBag) } private func generateUUID() { for _ in 0.. { $0.isEditable = false } let clearButton = SectionButton(image: R.Image.clear) override func layout() { super.layout() self.uuidView.snp.remakeConstraints{ make in make.height.equalTo(max(200, self.frame.height - 300)) } } override func onAwake() { self.addSection(Section(title: "Configuration".localized(), items: [ Area(icon: R.Image.hyphen, title: "Hyphens".localized(), control: hyphensSwitch), Area(icon: R.Image.format, title: "Uppercase".localized(), control: uppercaseSwitch), ])) self.addSection(NSStackView() => { $0.addArrangedSubview(generateCount) $0.addArrangedSubview(NSTextField(labelWithString: "x") => { $0.font = .monospacedSystemFont(ofSize: 12, weight: .medium) }) $0.addArrangedSubview(generateButton) }) self.addSection(Section(title: "UUIDs".localized(), items: [ uuidView ], toolbarItems: [clearButton])) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Icon Generator/Generators/AndroidIconGenerator.swift ================================================ // // AndroidIconGenerator.swift // DevToys // // Created by yuki on 2022/02/27. // import CoreUtil enum AndroidIconGenerator { static func make(item: ImageItem, templete: IconTemplete, to destinationURL: URL) -> IconGenerateTask { let image = templete.bake(image: item.image, scale: .x512) let complete = Promise.tryAsync{ guard let s48 = image.resizedAspectFit(to: [48, 48], fillColor: .clear).png, let s72 = image.resizedAspectFit(to: [72, 72], fillColor: .clear).png, let s96 = image.resizedAspectFit(to: [96, 96], fillColor: .clear).png, let s144 = image.resizedAspectFit(to: [144, 144], fillColor: .clear).png, let s192 = image.resizedAspectFit(to: [192, 192], fillColor: .clear).png, let s512 = image.resizedAspectFit(to: [512, 512], fillColor: .clear).png else { throw IconGenerateError.convertError } do { try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil) try s48.write(to: destinationURL.appendingPathComponent("icon_mdpi.png")) try s72.write(to: destinationURL.appendingPathComponent("icon_hdpi.png")) try s96.write(to: destinationURL.appendingPathComponent("icon_xhdpi.png")) try s144.write(to: destinationURL.appendingPathComponent("icon_xxhdpi.png")) try s192.write(to: destinationURL.appendingPathComponent("icon_xxxhdpi.png")) try s512.write(to: destinationURL.appendingPathComponent("play_store.png")) } catch { throw IconGenerateError.exportError(error) } } return .init(imageItem: item, complete: complete) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IOSIconGenerator.swift ================================================ // // IcnsGenerator.swift // DevToys // // Created by yuki on 2022/02/26. // import CoreUtil enum IosIconGenerator { struct ExportOptions: OptionSet { let rawValue: UInt64 static let iphone = ExportOptions(rawValue: 1 << 0) static let ipad = ExportOptions(rawValue: 1 << 1) static let carplay = ExportOptions(rawValue: 1 << 2) static let mac = ExportOptions(rawValue: 1 << 3) static let applewatch = ExportOptions(rawValue: 1 << 4) } static func make(item: ImageItem, options: ExportOptions, to destinationURL: URL) -> IconGenerateTask { let complete = Promise.tryAsync{ let image = item.image let fillColor = NSColor.black guard let s16 = image.resizedAspectFit(to: [16, 16], fillColor: fillColor).png, let s20 = image.resizedAspectFit(to: [20, 20], fillColor: fillColor).png, let s29 = image.resizedAspectFit(to: [29, 29], fillColor: fillColor).png, let s32 = image.resizedAspectFit(to: [32, 32], fillColor: fillColor).png, let s40 = image.resizedAspectFit(to: [40, 40], fillColor: fillColor).png, let s48 = image.resizedAspectFit(to: [48, 48], fillColor: fillColor).png, let s55 = image.resizedAspectFit(to: [55, 55], fillColor: fillColor).png, let s57 = image.resizedAspectFit(to: [57, 57], fillColor: fillColor).png, let s58 = image.resizedAspectFit(to: [58, 58], fillColor: fillColor).png, let s60 = image.resizedAspectFit(to: [60, 60], fillColor: fillColor).png, let s64 = image.resizedAspectFit(to: [64, 64], fillColor: fillColor).png, let s66 = image.resizedAspectFit(to: [66, 66], fillColor: fillColor).png, let s76 = image.resizedAspectFit(to: [76, 76], fillColor: fillColor).png, let s80 = image.resizedAspectFit(to: [80, 80], fillColor: fillColor).png, let s87 = image.resizedAspectFit(to: [87, 87], fillColor: fillColor).png, let s88 = image.resizedAspectFit(to: [88, 88], fillColor: fillColor).png, let s92 = image.resizedAspectFit(to: [92, 92], fillColor: fillColor).png, let s100 = image.resizedAspectFit(to: [100, 100], fillColor: fillColor).png, let s102 = image.resizedAspectFit(to: [102, 102], fillColor: fillColor).png, let s114 = image.resizedAspectFit(to: [114, 114], fillColor: fillColor).png, let s120 = image.resizedAspectFit(to: [120, 120], fillColor: fillColor).png, let s128 = image.resizedAspectFit(to: [128, 128], fillColor: fillColor).png, let s152 = image.resizedAspectFit(to: [152, 152], fillColor: fillColor).png, let s167 = image.resizedAspectFit(to: [167, 167], fillColor: fillColor).png, let s172 = image.resizedAspectFit(to: [172, 172], fillColor: fillColor).png, let s180 = image.resizedAspectFit(to: [180, 180], fillColor: fillColor).png, let s196 = image.resizedAspectFit(to: [196, 196], fillColor: fillColor).png, let s216 = image.resizedAspectFit(to: [216, 216], fillColor: fillColor).png, let s234 = image.resizedAspectFit(to: [234, 234], fillColor: fillColor).png, let s256 = image.resizedAspectFit(to: [256, 256], fillColor: fillColor).png, let s512 = image.resizedAspectFit(to: [512, 512], fillColor: fillColor).png, let s1024 = image.resizedAspectFit(to: [1024, 1024], fillColor: fillColor).png else { throw IconGenerateError.convertError } do { try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil) if options.contains(.iphone) { try s40.write(to: destinationURL.appendingPathComponent("iPhone Notification@2x.png")) try s60.write(to: destinationURL.appendingPathComponent("iPhone Notification@3x.png")) try s29.write(to: destinationURL.appendingPathComponent("iPhone Settings.png")) try s58.write(to: destinationURL.appendingPathComponent("iPhone Settings@2x.png")) try s87.write(to: destinationURL.appendingPathComponent("iPhone Settings@3x.png")) try s80.write(to: destinationURL.appendingPathComponent("iPhone Spotlight@2x.png")) try s120.write(to: destinationURL.appendingPathComponent("iPhone Spotlight@3x.png")) try s57.write(to: destinationURL.appendingPathComponent("iPhone App iOS 5,6.png")) try s114.write(to: destinationURL.appendingPathComponent("iPhone App iOS 5,6@2x.png")) try s120.write(to: destinationURL.appendingPathComponent("iPhone App@2x.png")) try s180.write(to: destinationURL.appendingPathComponent("iPhone App@3x.png")) } if options.contains(.ipad) { try s20.write(to: destinationURL.appendingPathComponent("iPad Notification.png")) try s40.write(to: destinationURL.appendingPathComponent("iPad Notification@2x.png")) try s29.write(to: destinationURL.appendingPathComponent("iPad Settings.png")) try s58.write(to: destinationURL.appendingPathComponent("iPad Settings@2x.png")) try s40.write(to: destinationURL.appendingPathComponent("iPad Spotlight.png")) try s80.write(to: destinationURL.appendingPathComponent("iPad Spotlight@2x.png")) try s76.write(to: destinationURL.appendingPathComponent("iPad App.png")) try s152.write(to: destinationURL.appendingPathComponent("iPad App@2x.png")) try s167.write(to: destinationURL.appendingPathComponent("iPad Pro App@2x.png")) } if options.contains(.iphone) || options.contains(.ipad) { try s1024.write(to: destinationURL.appendingPathComponent("App Store iOS.png")) } if options.contains(.carplay) { try s120.write(to: destinationURL.appendingPathComponent("CarPlay@2x.png")) try s180.write(to: destinationURL.appendingPathComponent("CarPlay@3x.png")) } if options.contains(.mac) { try s16.write(to: destinationURL.appendingPathComponent("Mac 16x16.png")) try s32.write(to: destinationURL.appendingPathComponent("Mac 16x16@2x.png")) try s32.write(to: destinationURL.appendingPathComponent("Mac 32x32.png")) try s64.write(to: destinationURL.appendingPathComponent("Mac 32x32@2x.png")) try s128.write(to: destinationURL.appendingPathComponent("Mac 128x128.png")) try s256.write(to: destinationURL.appendingPathComponent("Mac 128x128@2x.png")) try s256.write(to: destinationURL.appendingPathComponent("Mac 256x256.png")) try s512.write(to: destinationURL.appendingPathComponent("Mac 256x256@2x.png")) try s512.write(to: destinationURL.appendingPathComponent("Mac 512x512.png")) try s1024.write(to: destinationURL.appendingPathComponent("Mac 512x512@2x.png")) } if options.contains(.applewatch) { try s48.write(to: destinationURL.appendingPathComponent("Apple Watch Notification Center 48x28@2x.png")) try s55.write(to: destinationURL.appendingPathComponent("Apple Watch Notification Center 55x55@2x.png")) try s66.write(to: destinationURL.appendingPathComponent("Apple Watch Notification Center 66x66@2x.png")) try s58.write(to: destinationURL.appendingPathComponent("Apple Watch Companion Settings@2x.png")) try s87.write(to: destinationURL.appendingPathComponent("Apple Watch Companion Settings@3x.png")) try s80.write(to: destinationURL.appendingPathComponent("Apple Watch Home Screen 38mm@2x.png")) try s88.write(to: destinationURL.appendingPathComponent("Apple Watch Home Screen 40mm@2x.png")) try s92.write(to: destinationURL.appendingPathComponent("Apple Watch Home Screen 41mm@2x.png")) try s100.write(to: destinationURL.appendingPathComponent("Apple Watch Home Screen 44mm@2x.png")) try s102.write(to: destinationURL.appendingPathComponent("Apple Watch Home Screen 45mm@2x.png")) try s172.write(to: destinationURL.appendingPathComponent("Apple Watch Short Look 172x172@2x.png")) try s196.write(to: destinationURL.appendingPathComponent("Apple Watch Short Look 196x196@2x.png")) try s216.write(to: destinationURL.appendingPathComponent("Apple Watch Short Look 216x216@2x.png")) try s234.write(to: destinationURL.appendingPathComponent("Apple Watch Short Look 234x234@2x.png")) try s1024.write(to: destinationURL.appendingPathComponent("Apple Watch App Store.png")) } } catch { throw IconGenerateError.exportError(error) } } return .init(imageItem: item, complete: complete) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IcnsGenerator.swift ================================================ // // IcnsGenerator.swift // DevToys // // Created by yuki on 2022/02/26. // import CoreUtil enum IcnsGenerator { private static let iconutilURL = URL(fileURLWithPath: "/usr/bin/iconutil") private static let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent("IcnsGenerator") => { try? FileManager.default.createDirectory(at: $0, withIntermediateDirectories: true, attributes: nil) } static func make(item: ImageItem, templete: IconTemplete, to destinationURL: URL) -> IconGenerateTask { let complete = Promise .tryAsync{ let iconsetURL = temporaryDirectory.appendingPathComponent(UUID().uuidString).appendingPathExtension("iconset") try IconsetGenerator.generateIconset(image: item.image, templete: templete, to: iconsetURL) return iconsetURL } .flatPeek{ Terminal.run(iconutilURL, arguments: ["-c", "icns", "--output", destinationURL.path, $0.path]) } .tryPeek{ try FileManager.default.removeItem(at: $0) } .eraseToVoid() return IconGenerateTask(imageItem: item, complete: complete) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IcoGenerator.swift ================================================ // // IcoGenerator.swift // DevToys // // Created by yuki on 2022/02/28. // import CoreUtil enum IcoGenerator { static func make(item: ImageItem, templete: IconTemplete, to destinationURL: URL) -> IconGenerateTask { let image = templete.bake(image: item.image, scale: .x256) let complete = Promise.tryAsync{ let tmpURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString).png") try image.png?.write(to: tmpURL) return tmpURL } .flatPeek{ FFExecutor.execute([], inputURL: $0, destinationURL: destinationURL).eraseToError().flatMap{ $0.complete } } .tryPeek{ try FileManager.default.removeItem(at: $0) } .eraseToVoid() return IconGenerateTask(imageItem: item, complete: complete) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IconFolderGenerator.swift ================================================ // // IconFolderGenerator.swift // DevToys // // Created by yuki on 2022/02/26. // import CoreUtil import CoreGraphics enum IconFolderGenerator { static func make(item: ImageItem, templete: IconTemplete, to destinationURL: URL) -> IconGenerateTask { IconGenerateTask(imageItem: item, complete: .tryAsync{ try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil) let image = templete.bake(image: item.image, scale: .x512) NSWorkspace.shared.setIcon(image, forFile: destinationURL.path, options: .excludeQuickDrawElementsIconCreationOption) }) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IconGenerator+Model.swift ================================================ // // IconGenerator+Model.swift // DevToys // // Created by yuki on 2022/02/26. // import CoreUtil struct IconGenerateTask { let imageItem: ImageItem let complete: Promise } enum IconGenerateError: Error { case convertError case exportError(Error) } ================================================ FILE: DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IconsetGenerator.swift ================================================ // // IconsetGenerator.swift // DevToys // // Created by yuki on 2022/02/26. // import CoreUtil enum IconsetGenerator { static func make(item: ImageItem, templete: IconTemplete, to destinationURL: URL) -> IconGenerateTask { IconGenerateTask(imageItem: item, complete: .tryAsync{ try self.generateIconset(image: item.image, templete: templete, to: destinationURL) }) } static func generateIconset(image: NSImage, templete: IconTemplete, to destinationURL: URL) throws { let iconset = templete.bakeIconSet(image: image) do { try iconset.write(to: destinationURL) } catch { throw IconGenerateError.exportError(error) } return () } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IosIconGenerator.swift ================================================ // // IcnsGenerator.swift // DevToys // // Created by yuki on 2022/02/26. // import CoreUtil enum IosIconGenerator { struct ExportOptions: OptionSet { let rawValue: UInt64 static let iphone = ExportOptions(rawValue: 1 << 0) static let ipad = ExportOptions(rawValue: 1 << 1) static let carplay = ExportOptions(rawValue: 1 << 2) static let mac = ExportOptions(rawValue: 1 << 3) static let applewatch = ExportOptions(rawValue: 1 << 4) } static func make(item: ImageItem, options: ExportOptions, to destinationURL: URL) -> IconGenerateTask { let complete = Promise.tryAsync{ let image = item.image let fillColor = NSColor.black guard let s16 = image.resizedAspectFit(to: [16, 16], fillColor: fillColor).png, let s20 = image.resizedAspectFit(to: [20, 20], fillColor: fillColor).png, let s29 = image.resizedAspectFit(to: [29, 29], fillColor: fillColor).png, let s32 = image.resizedAspectFit(to: [32, 32], fillColor: fillColor).png, let s40 = image.resizedAspectFit(to: [40, 40], fillColor: fillColor).png, let s48 = image.resizedAspectFit(to: [48, 48], fillColor: fillColor).png, let s55 = image.resizedAspectFit(to: [55, 55], fillColor: fillColor).png, let s57 = image.resizedAspectFit(to: [57, 57], fillColor: fillColor).png, let s58 = image.resizedAspectFit(to: [58, 58], fillColor: fillColor).png, let s60 = image.resizedAspectFit(to: [60, 60], fillColor: fillColor).png, let s64 = image.resizedAspectFit(to: [64, 64], fillColor: fillColor).png, let s66 = image.resizedAspectFit(to: [66, 66], fillColor: fillColor).png, let s76 = image.resizedAspectFit(to: [76, 76], fillColor: fillColor).png, let s80 = image.resizedAspectFit(to: [80, 80], fillColor: fillColor).png, let s87 = image.resizedAspectFit(to: [87, 87], fillColor: fillColor).png, let s88 = image.resizedAspectFit(to: [88, 88], fillColor: fillColor).png, let s92 = image.resizedAspectFit(to: [92, 92], fillColor: fillColor).png, let s100 = image.resizedAspectFit(to: [100, 100], fillColor: fillColor).png, let s102 = image.resizedAspectFit(to: [102, 102], fillColor: fillColor).png, let s114 = image.resizedAspectFit(to: [114, 114], fillColor: fillColor).png, let s120 = image.resizedAspectFit(to: [120, 120], fillColor: fillColor).png, let s128 = image.resizedAspectFit(to: [128, 128], fillColor: fillColor).png, let s152 = image.resizedAspectFit(to: [152, 152], fillColor: fillColor).png, let s167 = image.resizedAspectFit(to: [167, 167], fillColor: fillColor).png, let s172 = image.resizedAspectFit(to: [172, 172], fillColor: fillColor).png, let s180 = image.resizedAspectFit(to: [180, 180], fillColor: fillColor).png, let s196 = image.resizedAspectFit(to: [196, 196], fillColor: fillColor).png, let s216 = image.resizedAspectFit(to: [216, 216], fillColor: fillColor).png, let s234 = image.resizedAspectFit(to: [234, 234], fillColor: fillColor).png, let s256 = image.resizedAspectFit(to: [256, 256], fillColor: fillColor).png, let s512 = image.resizedAspectFit(to: [512, 512], fillColor: fillColor).png, let s1024 = image.resizedAspectFit(to: [1024, 1024], fillColor: fillColor).png else { throw IconGenerateError.convertError } do { try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil) if options.contains(.iphone) { try s40.write(to: destinationURL.appendingPathComponent("iPhone Notification@2x.png")) try s60.write(to: destinationURL.appendingPathComponent("iPhone Notification@3x.png")) try s29.write(to: destinationURL.appendingPathComponent("iPhone Settings.png")) try s58.write(to: destinationURL.appendingPathComponent("iPhone Settings@2x.png")) try s87.write(to: destinationURL.appendingPathComponent("iPhone Settings@3x.png")) try s80.write(to: destinationURL.appendingPathComponent("iPhone Spotlight@2x.png")) try s120.write(to: destinationURL.appendingPathComponent("iPhone Spotlight@3x.png")) try s57.write(to: destinationURL.appendingPathComponent("iPhone App iOS 5,6.png")) try s114.write(to: destinationURL.appendingPathComponent("iPhone App iOS 5,6@2x.png")) try s120.write(to: destinationURL.appendingPathComponent("iPhone App@2x.png")) try s180.write(to: destinationURL.appendingPathComponent("iPhone App@3x.png")) } if options.contains(.ipad) { try s20.write(to: destinationURL.appendingPathComponent("iPad Notification.png")) try s40.write(to: destinationURL.appendingPathComponent("iPad Notification@2x.png")) try s29.write(to: destinationURL.appendingPathComponent("iPad Settings.png")) try s58.write(to: destinationURL.appendingPathComponent("iPad Settings@2x.png")) try s40.write(to: destinationURL.appendingPathComponent("iPad Spotlight.png")) try s80.write(to: destinationURL.appendingPathComponent("iPad Spotlight@2x.png")) try s76.write(to: destinationURL.appendingPathComponent("iPad App.png")) try s152.write(to: destinationURL.appendingPathComponent("iPad App@2x.png")) try s167.write(to: destinationURL.appendingPathComponent("iPad Pro App@2x.png")) } if options.contains(.iphone) || options.contains(.ipad) { try s1024.write(to: destinationURL.appendingPathComponent("App Store iOS.png")) } if options.contains(.carplay) { try s120.write(to: destinationURL.appendingPathComponent("CarPlay@2x.png")) try s180.write(to: destinationURL.appendingPathComponent("CarPlay@3x.png")) } if options.contains(.mac) { try s16.write(to: destinationURL.appendingPathComponent("Mac 16x16.png")) try s32.write(to: destinationURL.appendingPathComponent("Mac 16x16@2x.png")) try s32.write(to: destinationURL.appendingPathComponent("Mac 32x32.png")) try s64.write(to: destinationURL.appendingPathComponent("Mac 32x32@2x.png")) try s128.write(to: destinationURL.appendingPathComponent("Mac 128x128.png")) try s256.write(to: destinationURL.appendingPathComponent("Mac 128x128@2x.png")) try s256.write(to: destinationURL.appendingPathComponent("Mac 256x256.png")) try s512.write(to: destinationURL.appendingPathComponent("Mac 256x256@2x.png")) try s512.write(to: destinationURL.appendingPathComponent("Mac 512x512.png")) try s1024.write(to: destinationURL.appendingPathComponent("Mac 512x512@2x.png")) } if options.contains(.applewatch) { try s48.write(to: destinationURL.appendingPathComponent("Apple Watch Notification Center 48x28@2x.png")) try s55.write(to: destinationURL.appendingPathComponent("Apple Watch Notification Center 55x55@2x.png")) try s66.write(to: destinationURL.appendingPathComponent("Apple Watch Notification Center 66x66@2x.png")) try s58.write(to: destinationURL.appendingPathComponent("Apple Watch Companion Settings@2x.png")) try s87.write(to: destinationURL.appendingPathComponent("Apple Watch Companion Settings@3x.png")) try s80.write(to: destinationURL.appendingPathComponent("Apple Watch Home Screen 38mm@2x.png")) try s88.write(to: destinationURL.appendingPathComponent("Apple Watch Home Screen 40mm@2x.png")) try s92.write(to: destinationURL.appendingPathComponent("Apple Watch Home Screen 41mm@2x.png")) try s100.write(to: destinationURL.appendingPathComponent("Apple Watch Home Screen 44mm@2x.png")) try s102.write(to: destinationURL.appendingPathComponent("Apple Watch Home Screen 45mm@2x.png")) try s172.write(to: destinationURL.appendingPathComponent("Apple Watch Short Look 172x172@2x.png")) try s196.write(to: destinationURL.appendingPathComponent("Apple Watch Short Look 196x196@2x.png")) try s216.write(to: destinationURL.appendingPathComponent("Apple Watch Short Look 216x216@2x.png")) try s234.write(to: destinationURL.appendingPathComponent("Apple Watch Short Look 234x234@2x.png")) try s1024.write(to: destinationURL.appendingPathComponent("Apple Watch App Store.png")) } } catch { throw IconGenerateError.exportError(error) } } return .init(imageItem: item, complete: complete) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Icon Generator/Generators/PngIconGenerator.swift ================================================ // // PNGGenerator.swift // DevToys // // Created by yuki on 2022/02/28. // import Foundation enum PngIconGenerator { static func make(item: ImageItem, templete: IconTemplete, scale: IconSet.Scale, to destinationURL: URL) -> IconGenerateTask { IconGenerateTask(imageItem: item, complete: .tryAsync{ try templete.bake(image: item.image, scale: scale).png?.write(to: destinationURL) }) } } enum JpegIconGenerator { static func make(item: ImageItem, templete: IconTemplete, scale: IconSet.Scale, to destinationURL: URL) -> IconGenerateTask { IconGenerateTask(imageItem: item, complete: .tryAsync{ try templete.bake(image: item.image, scale: scale).jpeg?.write(to: destinationURL) }) } } enum GifIconGenerator { static func make(item: ImageItem, templete: IconTemplete, scale: IconSet.Scale, to destinationURL: URL) -> IconGenerateTask { IconGenerateTask(imageItem: item, complete: .tryAsync{ try templete.bake(image: item.image, scale: scale).gif?.write(to: destinationURL) }) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Icon Generator/Icon Templete/IconImageManager.swift ================================================ // // IconImageManager.swift // DevToys // // Created by yuki on 2022/02/28. // import CoreUtil final class IconImageManager { var templetes = [IconTemplete]() private var templeteMap = [String: IconTemplete]() func templete(for identifier: String) -> IconTemplete? { self.templeteMap[identifier] } func register(_ templete: IconTemplete) { self.templetes.append(templete) self.templeteMap[templete.identifier] = templete } } extension IconImageManager { static let shared = IconImageManager() => { $0.register(OriginalIconTemplete()) $0.register(FolderFillIconTemplete()) $0.register(FolderCenterIconTemplete()) $0.register(FolderCenterEngravedIconTemplete()) $0.register(BigSurFillIconTemplete()) $0.register(AndroidIconTemplete()) $0.register(CircleIconTemplete()) $0.register(RoundedRectIconTemplete()) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Icon Generator/Icon Templete/IconSet.swift ================================================ // // IconSet.swift // DevToys // // Created by yuki on 2022/02/27. // import CoreUtil struct IconSet { enum Scale: Int { case x16, x32, x64, x128, x256, x512, x1024 var size: CGFloat { switch self { case .x16: return 16 case .x32: return 32 case .x64: return 64 case .x128: return 128 case .x256: return 256 case .x512: return 512 case .x1024: return 1024 } } var point: Int { Int(size) } } let icon16: NSImage let icon32: NSImage let icon64: NSImage let icon128: NSImage let icon256: NSImage let icon512: NSImage let icon1024: NSImage func image(for scale: Scale) -> NSImage { switch scale { case .x16: return icon16 case .x32: return icon32 case .x64: return icon64 case .x128: return icon128 case .x256: return icon256 case .x512: return icon512 case .x1024: return icon1024 } } init(icon16: NSImage, icon32: NSImage, icon64: NSImage, icon128: NSImage, icon256: NSImage, icon512: NSImage, icon1024: NSImage) { self.icon16 = icon16 self.icon32 = icon32 self.icon64 = icon64 self.icon128 = icon128 self.icon256 = icon256 self.icon512 = icon512 self.icon1024 = icon1024 } init?(make: (Scale) -> NSImage?) { guard let icon16 = make(.x16), let icon32 = make(.x32), let icon64 = make(.x64), let icon128 = make(.x128), let icon256 = make(.x256), let icon512 = make(.x512), let icon1024 = make(.x1024) else { return nil } self.init(icon16: icon16, icon32: icon32, icon64: icon64, icon128: icon128, icon256: icon256, icon512: icon512, icon1024: icon1024) } init?(bundleResouces: (Scale) -> String) { self.init(make: { Bundle.main.image(forResource: bundleResouces($0)) }) } } extension IconSet { func write(to url: URL) throws { try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) try self.icon16.png?.write(to: url.appendingPathComponent("icon_16x16.png")) try self.icon32.png?.write(to: url.appendingPathComponent("icon_16x16@2x.png")) try self.icon32.png?.write(to: url.appendingPathComponent("icon_32x32.png")) try self.icon64.png?.write(to: url.appendingPathComponent("icon_32x32@2x.png")) try self.icon64.png?.write(to: url.appendingPathComponent("icon_64x64.png")) try self.icon128.png?.write(to: url.appendingPathComponent("icon_64x64@2x.png")) try self.icon128.png?.write(to: url.appendingPathComponent("icon_128x128.png")) try self.icon256.png?.write(to: url.appendingPathComponent("icon_128x128@2x.png")) try self.icon256.png?.write(to: url.appendingPathComponent("icon_256x256.png")) try self.icon512.png?.write(to: url.appendingPathComponent("icon_256x256@2x.png")) try self.icon512.png?.write(to: url.appendingPathComponent("icon_512x512.png")) try self.icon1024.png?.write(to: url.appendingPathComponent("icon_512x512@2x.png")) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Icon Generator/Icon Templete/IconTemplete+.swift ================================================ // // FolderFillIconImage.swift // DevToys // // Created by yuki on 2022/02/27. // import CoreUtil import CoreGraphics struct OriginalIconTemplete: IconTemplete { let title: String = "Original" let identifier: String = "original" func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { return image.resizedAspectFit(to: [scale.size, scale.size], fillColor: .clear) } } struct RoundedRectIconTemplete: IconTemplete { let title: String = "Rounded" let identifier: String = "rounded" func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { let size: CGSize = [scale.size, scale.size] let scaleFactor = scale.size / 1024 return NSImage(size: size, cgcanvas: { context in let imageRect = CGRect(size: size).slimmed(by: 64 * scaleFactor) let image = IconTempleteHelper.fillToRect(rect: imageRect, image: image, size: size).cgImage! let cornerRadius = 64 * scaleFactor let circlePath = CGPath(roundedRect: imageRect, cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil) context.saveGState() context.setFillColor(.white) context.addPath(circlePath) context.setShadow(offset: [0, -7] * scaleFactor, blur: 20 * scaleFactor, color: NSColor.black.withAlphaComponent(0.5).cgColor) context.fillPath() context.restoreGState() context.addPath(circlePath) context.clip() context.draw(image, in: context.bounds) context.resetClip() }) } } struct CircleIconTemplete: IconTemplete { let title: String = "Circle" let identifier: String = "circle" func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { let size: CGSize = [scale.size, scale.size] let scaleFactor = scale.size / 1024 return NSImage(size: size, cgcanvas: { context in let imageRect = CGRect(size: size).slimmed(by: 64 * scaleFactor) let image = IconTempleteHelper.fillToRect(rect: imageRect, image: image, size: size).cgImage! let circlePath = CGPath(ellipseIn: imageRect, transform: nil) context.saveGState() context.setFillColor(.white) context.addPath(circlePath) context.setShadow(offset: [0, -7] * scaleFactor, blur: 20 * scaleFactor, color: NSColor.black.withAlphaComponent(0.5).cgColor) context.fillPath() context.restoreGState() context.addPath(circlePath) context.clip() context.draw(image, in: context.bounds) context.resetClip() }) } } struct AndroidIconTemplete: IconTemplete { let title: String = "Android" let identifier: String = "android" private static let mask = IconSet(make: { Bundle.main.image(forResource: "android_mask.png")!.resized(to: [$0.size, $0.size]) })! func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { let mask = Self.mask.image(for: scale) let size: CGSize = [scale.size, scale.size] return NSImage(size: size, cgcanvas: { context in let image = IconTempleteHelper.fillToRect(rect: CGRect(size: size), image: image, size: size).cgImage! context.clip(to: context.bounds, mask: mask.cgImage!.convertToGrayscale()) context.setFillColor(.white) context.fill(context.bounds) context.draw(image, in: context.bounds) }) } } struct BigSurFillIconTemplete: IconTemplete { let title: String = "Big Sur Icon" let identifier: String = "bigsur_icon" private static let background = IconSet(bundleResouces: { "squircle_back_\($0.point)x\($0.point).png" })! private static let mask = IconSet(bundleResouces: { "squircle_mask_\($0.point)x\($0.point).png" })! func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { let background = Self.background.image(for: scale) let mask = Self.mask.image(for: scale) return NSImage(size: background.size, cgcanvas: { context in let image = IconTempleteHelper.fillToRect(rect: folderRect(scale), image: image, size: background.size).cgImage! context.draw(background.cgImage!, in: context.bounds) context.clip(to: context.bounds, mask: mask.cgImage!.convertToGrayscale()) context.draw(image, in: context.bounds) }) } private func folderRect(_ scale: IconSet.Scale) -> CGRect { CGRect(origin: [100, 100] * (scale.size / 1024), size: [824, 824] * (scale.size / 1024)) } } struct FolderCenterEngravedIconTemplete: IconTemplete { let title: String = "Folder (Engraved)" let identifier: String = "folder_engraved" let embossColor = NSColor(patternImage: NSImage(named: "watermark_mask_bs.png")!) func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { let background = backgroundIconSet.image(for: scale) return NSImage(size: background.size, cgcanvas: { context in let sizeImage = IconTempleteHelper.fitToRect(rect: imageRect(scale), image: image, size: background.size) context.draw(background.cgImage!, in: context.bounds) let maskImage = maskImage(image: sizeImage, color: embossColor.cgColor, size: background.size).cgImage! context.setShadow(offset: [0, -2] * (scale.size / 512), blur: 2 * (scale.size / 512), color: NSColor.white.withAlphaComponent(0.2).cgColor) context.draw(maskImage, in: context.bounds) }) } private func maskImage(image: NSImage, color: CGColor, size: CGSize) -> NSImage { NSImage(size: size, cgcanvas: { context in context.setFillColor(.clear) context.fill(context.bounds) context.clip(to: context.bounds, mask: image.cgImage!) context.setFillColor(color) context.fill(context.bounds) }) } private func imageRect(_ scale: IconSet.Scale) -> CGRect { switch scale { case .x1024: return CGRect(origin: [248, 235], size: [530, 530]) default: return CGRect(origin: [130, 115] * (scale.size / 512), size: [255, 255] * (scale.size / 512)) } } } struct FolderCenterIconTemplete: IconTemplete { let title: String = "Folder (Center)" let identifier: String = "folder_center" func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { let background = backgroundIconSet.image(for: scale) return NSImage(size: background.size, cgcanvas: { context in let image = IconTempleteHelper.fitToRect(rect: imageRect(scale), image: image, size: background.size).cgImage! context.draw(background.cgImage!, in: context.bounds) context.draw(image, in: context.bounds) }) } private func imageRect(_ scale: IconSet.Scale) -> CGRect { switch scale { case .x1024: return CGRect(origin: [248, 235], size: [530, 530]) default: return CGRect(origin: [130, 115] * (scale.size / 512), size: [255, 255] * (scale.size / 512)) } } } struct FolderFillIconTemplete: IconTemplete { let title: String = "Folder (Fill)" let identifier: String = "folder_fill" func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { let background = backgroundIconSet.image(for: scale) let mask = maskIconSet.image(for: scale) let top = topIconSet.image(for: scale) return NSImage(size: background.size, cgcanvas: { context in let image = IconTempleteHelper.fillToRect(rect: folderRect(scale), image: image, size: background.size).cgImage! context.draw(background.cgImage!, in: context.bounds) context.clip(to: context.bounds, mask: mask.cgImage!.convertToGrayscale()) context.draw(image, in: context.bounds) context.resetClip() context.clip(to: context.bounds, mask: image) context.setAlpha(0.75) context.draw(top.cgImage!, in: context.bounds) }) } private func folderRect(_ scale: IconSet.Scale) -> CGRect { switch scale { case .x1024: return CGRect(origin: [24, 113], size: [974, 820]) default: return CGRect(origin: [20, 55] * (scale.size / 512), size: [471, 397] * (scale.size / 512)) } } } enum IconTempleteHelper { static func fillToRect(rect: CGRect, image: NSImage, size: CGSize) -> NSImage { NSImage(size: size, cgcanvas: { context in let imageSize = image.size * image.size.aspectFillRatio(fillInside: rect.size) let imageRect = CGRect(center: rect.center, size: imageSize) image.draw(in: imageRect, from: NSRect(size: image.size), operation: .sourceOver, fraction: 1) }) } static func fitToRect(rect: CGRect, image: NSImage, size: CGSize) -> NSImage { NSImage(size: size, cgcanvas: { context in let imageSize = image.size * image.size.aspectFitRatio(fitInside: rect.size) let imageRect = CGRect(center: rect.center, size: imageSize) image.draw(in: imageRect, from: NSRect(size: image.size), operation: .sourceOver, fraction: 1) }) } } private let backgroundIconSet = IconSet(bundleResouces: { "folder_back_\($0.point)_bs.png" })! private let maskIconSet = IconSet(bundleResouces: { "folder_mask2_\($0.point)_bs.png" })! private let topIconSet = IconSet( icon16: Bundle.main.image(forResource: "folder_top_512.png")!.resized(to: [16, 16]), icon32: Bundle.main.image(forResource: "folder_top_512.png")!.resized(to: [32, 32]), icon64: Bundle.main.image(forResource: "folder_top_512.png")!.resized(to: [64, 64]), icon128: Bundle.main.image(forResource: "folder_top_512.png")!.resized(to: [128, 128]), icon256: Bundle.main.image(forResource: "folder_top_512.png")!.resized(to: [256, 256]), icon512: Bundle.main.image(forResource: "folder_top_512.png")!, icon1024: Bundle.main.image(forResource: "folder_top_1024.png")! ) ================================================ FILE: DevToys/DevToys/Body/Graphic/Icon Generator/Icon Templete/IconTemplete.swift ================================================ // // IconImageManager+.swift // DevToys // // Created by yuki on 2022/02/27. // import CoreUtil protocol IconTemplete { var title: String { get } var identifier: String { get } func bake(image: NSImage, scale: IconSet.Scale) -> NSImage } extension IconTemplete { func bakeIconSet(image: NSImage) -> IconSet { IconSet(make: { bake(image: image, scale: $0) })! } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Icon Generator/IconGeneratorView+.swift ================================================ // // IconGeneratorView+.swift // DevToys // // Created by yuki on 2022/02/26. // import CoreUtil final class IconGeneratorViewController: NSViewController { private let cell = IconGeneratorView() @RestorableState("icongen.exporttype") var exportType: IconExportType = .icns @RestorableState("icongen.iosopt") var iosOptions: IosIconGenerator.ExportOptions = [.iphone, .ipad] @RestorableState("icongen.scale") var scale: IconSet.Scale = .x1024 @RestorableData("icongen.image") var imageItem: ImageItem? = nil @RestorableData("icongen.templete") var templeteName = "original" @Observable var iconTemplete: IconTemplete? = nil { didSet { templeteName = iconTemplete?.identifier ?? "original" } } @Observable var previewImage: NSImage? = nil let iconTempleteManager = IconImageManager.shared override func loadView() { self.view = cell } override func viewDidLoad() { self.$previewImage .sink{[unowned self] in self.cell.imageDropView.image = $0 }.store(in: &objectBag) self.$exportType .sink{[unowned self] in self.cell.exportTypePicker.selectedItem = $0 }.store(in: &objectBag) self.$iosOptions .sink{[unowned self] in self.bindiOSOptionsView($0) }.store(in: &objectBag) self.$iconTemplete .sink{[unowned self] in self.cell.iconTempletePicker.selectedMenuTitle = $0?.title }.store(in: &objectBag) self.$scale .sink{[unowned self] in self.cell.scalePicker.selectedItem = $0 }.store(in: &objectBag) self.cell.clearButton.actionPublisher .sink{[unowned self] in self.imageItem = nil; updatePreviewImage() }.store(in: &objectBag) self.cell.scalePicker.itemPublisher .sink{[unowned self] in self.scale = $0 }.store(in: &objectBag) self.cell.imageDropView.imagePublisher .sink{[unowned self] in self.imageItem = $0; updatePreviewImage() }.store(in: &objectBag) self.cell.exportTypePicker.itemPublisher .sink{[unowned self] in self.exportType = $0; updateOptionView() }.store(in: &objectBag) self.cell.exportButton.actionPublisher .sink{[unowned self] in self.exportIcon() }.store(in: &objectBag) self.cell.iosOptionsView.iPhoneSwitch.isOnPublisher .sink{[unowned self] in self.iosOptions = $0 ? iosOptions.union(.iphone) : iosOptions.subtracting(.iphone) }.store(in: &objectBag) self.cell.iosOptionsView.iPadSwitch.isOnPublisher .sink{[unowned self] in self.iosOptions = $0 ? iosOptions.union(.ipad) : iosOptions.subtracting(.ipad) }.store(in: &objectBag) self.cell.iosOptionsView.appleWatchSwitch.isOnPublisher .sink{[unowned self] in self.iosOptions = $0 ? iosOptions.union(.applewatch) : iosOptions.subtracting(.applewatch) }.store(in: &objectBag) self.cell.iosOptionsView.carplaySwitch.isOnPublisher .sink{[unowned self] in self.iosOptions = $0 ? iosOptions.union(.carplay) : iosOptions.subtracting(.carplay) }.store(in: &objectBag) self.cell.iosOptionsView.macSwitch.isOnPublisher .sink{[unowned self] in self.iosOptions = $0 ? iosOptions.union(.mac) : iosOptions.subtracting(.mac) }.store(in: &objectBag) for templete in self.iconTempleteManager.templetes { self.cell.iconTempletePicker.menuItems.append(NSMenuItem(title: templete.title) { self.iconTemplete = templete self.updatePreviewImage() }) } self.iconTemplete = self.iconTempleteManager.templete(for: self.templeteName) ?? self.iconTempleteManager.templetes.first self.updatePreviewImage() self.updateOptionView() } private func updatePreviewImage() { guard let imageItem = imageItem else { return previewImage = nil } if let iconTemplete = iconTemplete { self.previewImage = iconTemplete.bake(image: imageItem.image, scale: .x512) } else { self.previewImage = imageItem.image } } private func bindiOSOptionsView(_ options: IosIconGenerator.ExportOptions) { self.cell.iosOptionsView.iPhoneSwitch.isOn = options.contains(.iphone) self.cell.iosOptionsView.iPadSwitch.isOn = options.contains(.ipad) self.cell.iosOptionsView.appleWatchSwitch.isOn = options.contains(.applewatch) self.cell.iosOptionsView.carplaySwitch.isOn = options.contains(.carplay) self.cell.iosOptionsView.macSwitch.isOn = options.contains(.mac) } private func updateOptionView() { self.cell.iosOptionsView.isHidden = true self.cell.scalePickerArea.isHidden = true switch self.exportType { case .iosAssets: self.cell.iosOptionsView.isHidden = false case .png, .jpeg, .gif: self.cell.scalePickerArea.isHidden = false default: break } } private func exportIcon() { guard let item = imageItem, let filename = exportFilename(), let templete = iconTemplete else { return } let panel = NSSavePanel() panel.nameFieldStringValue = filename guard panel.runModal() == .OK, let url = panel.url else { return } switch self.exportType { case .iconFolder: IconFolderGenerator.make(item: item, templete: templete, to: url) => registerTask(_:) case .iosAssets: IosIconGenerator.make(item: item, options: iosOptions, to: url) => registerTask(_:) case .icns: IcnsGenerator.make(item: item, templete: templete, to: url) => registerTask(_:) case .iconset: IconsetGenerator.make(item: item, templete: templete, to: url) => registerTask(_:) case .androidAssets: AndroidIconGenerator.make(item: item, templete: templete, to: url) => registerTask(_:) case .ico: IcoGenerator.make(item: item, templete: templete, to: url) => registerTask(_:) case .png: PngIconGenerator.make(item: item, templete: templete, scale: scale, to: url) => registerTask(_:) case .jpeg: JpegIconGenerator.make(item: item, templete: templete, scale: scale, to: url) => registerTask(_:) case .gif: GifIconGenerator.make(item: item, templete: templete, scale: scale, to: url) => registerTask(_:) } } private func registerTask(_ task: IconGenerateTask) { task.complete .peekProgressIndicator("Generating Icon...") .sinkWithToast({ "Icon Exported!" }, {_ in "Icon Export Failed!" }) } private func exportFilename() -> String? { guard let item = imageItem else { return nil } switch self.exportType { case .icns: return "\(item.filenameWithoutExtension).icns" case .iconFolder: return item.filenameWithoutExtension case .iconset: return "\(item.filenameWithoutExtension).iconset" case .iosAssets: return "\(item.filenameWithoutExtension) (iOS Icon)" case .androidAssets: return "\(item.filenameWithoutExtension) (Android Icon)" case .ico: return "\(item.filenameWithoutExtension).ico" case .png: return "\(item.filenameWithoutExtension).png" case .jpeg: return "\(item.filenameWithoutExtension).jpg" case .gif: return "\(item.filenameWithoutExtension).gif" } } } enum IconExportType: String, TextItem { case iconFolder = "Icon Folder" case iosAssets = "iOS" case androidAssets = "Android" case icns = "ICNS" case iconset = "Icon Set" case ico = "ICO" case png = "PNG" case jpeg = "JPEG" case gif = "GIF" var title: String { rawValue } } extension IconSet.Scale: TextItem { static let allCases: [IconSet.Scale] = [.x16, .x32, .x64, .x128, .x256, .x512, .x1024] var title: String { switch self { case .x16: return "16px" case .x32: return "32px" case .x64: return "64px" case .x128: return "128px" case .x256: return "256px" case .x512: return "512px" case .x1024: return "1024px" } } } final private class IconGeneratorView: Page { let exportTypePicker = EnumPopupButton() let iconTempletePicker = PopupButton() let imageDropView = ImageDropView() let iosOptionsView = iOSOptionsView() let scalePicker = EnumPopupButton() lazy var scalePickerArea = Area(icon: R.Image.paramators, title: "Scale", control: scalePicker) let clearButton = SectionButton(title: "Clear".localized(), image: R.Image.clear) let exportButton = Button(title: "Export") override func onAwake() { self.addSection(Section(title: "Configuration".localized(), items: [ Area(icon: R.Image.export, title: "Icon Export Type", control: exportTypePicker), Area(icon: R.Image.paramators, title: "Templetes", control: iconTempletePicker) ])) self.addSection2( Section(title: "Source".localized(), items: [ imageDropView => { $0.snp.makeConstraints{ make in make.height.equalTo(320) } } ], toolbarItems: [clearButton]), Section(title: "Options", items: [ scalePickerArea, iosOptionsView, exportButton ]) ) } } final private class iOSOptionsView: NSLoadStackView { let iPhoneSwitch = NSSwitch() let iPadSwitch = NSSwitch() let appleWatchSwitch = NSSwitch() let carplaySwitch = NSSwitch() let macSwitch = NSSwitch() override func onAwake() { self.orientation = .vertical self.addArrangedSubview(Area(icon: R.Image.iphone, title: "iPhone", control: iPhoneSwitch)) self.addArrangedSubview(Area(icon: R.Image.iphone, title: "iPad", control: iPadSwitch)) self.addArrangedSubview(Area(icon: R.Image.appleWatch, title: "Apple Watch", control: appleWatchSwitch)) self.addArrangedSubview(Area(icon: R.Image.carplay, title: "CarPlay", control: carplaySwitch)) self.addArrangedSubview(Area(icon: R.Image.mac, title: "Mac", control: macSwitch)) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Image Converter/Exporters/DefaultImageExporter.swift ================================================ // // DefaultImageExporter.swift // DevToys // // Created by yuki on 2022/02/18. // import CoreUtil enum ImageExportError: Error { case nonData } enum DefaultImageExporter { static func exportPNG(_ image: NSImage, to url: URL) -> Promise { .tryAsync { guard let data = image.png else { throw ImageExportError.nonData } try data.write(to: url) } } static func exportGIF(_ image: NSImage, to url: URL) -> Promise { .tryAsync { guard let data = image.gif else { throw ImageExportError.nonData } try data.write(to: url) } } static func exportJPEG(_ image: NSImage, to url: URL) -> Promise { .tryAsync { guard let data = image.jpeg else { throw ImageExportError.nonData } try data.write(to: url) } } static func exportTIFF(_ image: NSImage, to url: URL) -> Promise { .tryAsync { guard let data = image.tiffRepresentation else { throw ImageExportError.nonData } try data.write(to: url) } } } extension NSImage { public var png: Data? { self.data(for: .png) } public var jpeg: Data? { self.data(for: .jpeg) } public var gif: Data? { self.data(for: .gif) } public convenience init(cgImage: CGImage) { self.init(cgImage: cgImage, size: cgImage.size) } public func data(for fileType: NSBitmapImageRep.FileType, properties: [NSBitmapImageRep.PropertyKey : Any] = [:]) -> Data? { guard let tiffRepresentation = self.tiffRepresentation, let bitmap = NSBitmapImageRep(data: tiffRepresentation), let rep = bitmap.representation(using: fileType, properties: properties) else { return nil } return rep } } extension CGImage { public var size: CGSize { CGSize(width: self.width, height: self.height) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Image Converter/Exporters/HeicImageExporter.swift ================================================ // // HeicConverter.swift // DevToys // // Created by yuki on 2022/02/18. // import AVFoundation import CoreUtil enum HeicExportError: Error { case heicNotSupported case cgImageMissing case couldNotFinalize } enum HeicImageExporter { static func export(_ image: NSImage, to url: URL) -> Promise { Promise.tryAsync{ let data = NSMutableData() guard let cgImage = image.cgImage else { throw HeicExportError.cgImageMissing } guard let destination = CGImageDestinationCreateWithData(data, AVFileType.heic as CFString, 1, nil) else { throw HeicExportError.heicNotSupported } CGImageDestinationAddImage(destination, cgImage, [:] as CFDictionary) guard CGImageDestinationFinalize(destination) else { throw HeicExportError.couldNotFinalize } try data.write(to: url) } } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Image Converter/Exporters/WebpImageExporter.swift ================================================ // // WebpExporter.swift // DevToys // // Created by yuki on 2022/02/18. // import CoreUtil enum WebpExportError: Error { case noImageData } enum WebpImageExporter { private static let cwebpURL = Bundle.current.url(forResource: "cwebp", withExtension: nil)! private static let inputDataURL = FileManager.temporaryDirectoryURL.appendingPathComponent("WebpInputData") => { try? FileManager.default.createDirectory(at: $0, withIntermediateDirectories: true, attributes: nil) } static func export(_ image: NSImage, to url: URL) -> Promise { Promise.tryAsync{ let url = inputDataURL.appendingPathComponent(UUID().uuidString) guard let data = image.tiffRepresentation else { throw WebpExportError.noImageData } try data.write(to: url) return url } .flatPeek{ Terminal.run(cwebpURL, arguments: [$0.path, "-o", url.path]) } .tryPeek{ try FileManager.default.removeItem(at: $0) } .eraseToVoid() } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Image Converter/ImageConverter.swift ================================================ // // ImageConverter.swift // DevToys // // Created by yuki on 2022/02/04. // import CoreUtil struct ImageConvertTask { let image: NSImage let title: String let size: CGSize let destinationURL: URL let isDone: Promise } enum ImageConverter { private static let destinationDirectory = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask)[0].appendingPathComponent("DevToys") => { try? FileManager.default.createDirectory(at: $0, withIntermediateDirectories: true, attributes: nil) } static func convert(_ item: ImageItem, format: ImageFormatType, resize: Bool, size: CGSize, scale: ImageScaleMode) -> ImageConvertTask { var image = item.image if resize { switch scale { case .scaleToFill: image = image.resizedAspectFill(to: size) case .scaleToFit: image = image.resizedAspectFit(to: size) } } let filename = "\(item.filenameWithoutExtension).\(format.exp)" let destinationURL = destinationDirectory.appendingPathComponent(filename) let isDone = exportPromise(image, to: format, destinationURL: destinationURL) .receive(on: .main) .peek{ NSWorkspace.shared.activateFileViewerSelecting([destinationURL]) } return ImageConvertTask(image: item.image, title: item.filename, size: item.image.size, destinationURL: destinationURL, isDone: isDone) } private static func exportPromise(_ image: NSImage, to format: ImageFormatType, destinationURL: URL) -> Promise { switch format { case .webp: return WebpImageExporter.export(image, to: destinationURL) case .heic: return HeicImageExporter.export(image, to: destinationURL) case .png: return DefaultImageExporter.exportPNG(image, to: destinationURL) case .jpg: return DefaultImageExporter.exportJPEG(image, to: destinationURL) case .gif: return DefaultImageExporter.exportGIF(image, to: destinationURL) case .tiff: return DefaultImageExporter.exportTIFF(image, to: destinationURL) } } } enum ImageFormatType: String, TextItem { case png = "PNG Format" case jpg = "JPEG Format" case tiff = "TIFF Format" case gif = "GIF Format" case webp = "Webp Format" case heic = "Heic Format" var title: String { rawValue.localized() } var exp: String { switch self { case .png: return "png" case .jpg: return "jpg" case .gif: return "gif" case .tiff: return "tiff" case .webp: return "webp" case .heic: return "heic" } } } enum ImageScaleMode: String, TextItem { case scaleToFill = "Scale to Fill" case scaleToFit = "Scale to Fit" var title: String { rawValue.localized() } } extension NSImage { func resizedAspectFill(to newSize: CGSize) -> NSImage { let bitmapRep = NSBitmapImageRep(size: newSize) let scale = self.size.aspectFillRatio(fillInside: newSize) NSGraphicsContext(bitmapImageRep: bitmapRep)?.perform { self.draw(in: CGRect(center: newSize.convertToPoint()/2, size: self.size * scale), from: .zero, operation: .copy, fraction: 1.0) } return NSImage(bitmapImageRep: bitmapRep) } func resizedAspectFit(to newSize: CGSize, fillColor: NSColor = .black) -> NSImage { let bitmapRep = NSBitmapImageRep(size: newSize) let scale = self.size.aspectFitRatio(fitInside: newSize) NSGraphicsContext(bitmapImageRep: bitmapRep)?.perform { fillColor.setFill() NSRect(size: newSize).fill() draw(in: CGRect(center: newSize.convertToPoint()/2, size: self.size * scale), from: .zero, operation: .sourceOver, fraction: 1.0) } return NSImage(bitmapImageRep: bitmapRep) } func resized(to newSize: NSSize) -> NSImage { let bitmapRep = NSBitmapImageRep(size: newSize) NSGraphicsContext(bitmapImageRep: bitmapRep)?.perform { draw(in: NSRect(x: 0, y: 0, width: newSize.width, height: newSize.height), from: .zero, operation: .copy, fraction: 1.0) } return NSImage(bitmapImageRep: bitmapRep) } } extension NSGraphicsContext { public func perform(_ block: () -> ()) { NSGraphicsContext.saveGraphicsState() NSGraphicsContext.current = self block() NSGraphicsContext.restoreGraphicsState() } } extension NSImage { public convenience init(bitmapImageRep: NSBitmapImageRep) { self.init(size: bitmapImageRep.size) self.addRepresentation(bitmapImageRep) } public convenience init(size: CGSize, colorSpaceName: NSColorSpaceName = .calibratedRGB, canvas: () -> ()) { let bitmapImageRep = NSBitmapImageRep(size: size, colorSpaceName: colorSpaceName) NSGraphicsContext(bitmapImageRep: bitmapImageRep)?.perform(canvas) self.init(bitmapImageRep: bitmapImageRep) } public convenience init(size: CGSize, colorSpaceName: NSColorSpaceName = .calibratedRGB, cgcanvas: (CGContext) -> ()) { self.init(size: size, colorSpaceName: colorSpaceName, canvas: { if let context = NSGraphicsContext.current?.cgContext { cgcanvas(context) } }) } } extension NSBitmapImageRep { public convenience init(size: CGSize, colorSpaceName: NSColorSpaceName = .calibratedRGB) { self.init( bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: colorSpaceName, bytesPerRow: 0, bitsPerPixel: 0 )! } } extension CGContext { public var size: CGSize { CGSize(width: width, height: height) } public var bounds: CGRect { CGRect(size: size) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Image Converter/ImageConverterView+.swift ================================================ // // ImageConverter.swift // DevToys // // Created by yuki on 2022/02/04. // import CoreUtil final class ImageConverterViewController: NSViewController { private let cell = ImageConverterView() @RestorableState("imc.format") private var format = ImageFormatType.png @RestorableState("imc.scaleMode") private var scaleMode = ImageScaleMode.scaleToFill @RestorableState("imc.resize") private var resize = false @RestorableState("imc.width") private var width = 1280.0 @RestorableState("imc.height") private var height = 720.0 @Observable var task: [ImageConvertTask] = [] override func loadView() { self.view = cell } override func viewDidLoad() { self.$format .sink{[unowned self] in cell.formatTypePicker.selectedItem = $0 }.store(in: &objectBag) self.$task .sink{[unowned self] in cell.listView.convertTasks = $0 }.store(in: &objectBag) self.$resize .sink{[unowned self] in cell.resizeSwitch.isOn = $0; cell.resizeOptionStack.isHidden = !$0 }.store(in: &objectBag) self.$scaleMode .sink{[unowned self] in cell.scaleModePicker.selectedItem = $0 }.store(in: &objectBag) self.$width .sink{[unowned self] in self.cell.widthField.value = $0 }.store(in: &objectBag) self.$height .sink{[unowned self] in self.cell.heightField.value = $0 }.store(in: &objectBag) self.cell.resizeSwitch.isOnPublisher .sink{[unowned self] in self.resize = $0 }.store(in: &objectBag) self.cell.widthField.valuePublisher .sink{[unowned self] in self.width = $0.reduce(width).clamped(1...) }.store(in: &objectBag) self.cell.heightField.valuePublisher .sink{[unowned self] in self.height = $0.reduce(height).clamped(1...) }.store(in: &objectBag) self.cell.scaleModePicker.itemPublisher .sink{[unowned self] in self.scaleMode = $0 }.store(in: &objectBag) self.cell.formatTypePicker.itemPublisher .sink{[unowned self] in self.format = $0 }.store(in: &objectBag) self.cell.dragPublisher .sink{[unowned self] in self.readURLs($0) }.store(in: &objectBag) } private func readURLs(_ pasteboard: NSPasteboard) { let newImageItems = ImageDropper.images(fromPasteboard: pasteboard) guard !newImageItems.isEmpty else { return } self.task.append(contentsOf: newImageItems.map{ ImageConverter.convert($0, format: format, resize: self.resize, size: [CGFloat(width), CGFloat(height)], scale: scaleMode) }) self.cell.listView.scrollView.contentView.scrollToBottom() } } final private class ImageConverterView: Page { let formatTypePicker = EnumPopupButton() let resizeSwitch = NSSwitch() let widthField = NumberField() let heightField = NumberField() let scaleModePicker = EnumPopupButton() let listView = ImageListView() let dragPublisher = PassthroughSubject() lazy var resizeOptionStack = NSStackView() => { let spacer = NSView() $0.distribution = .fillProportionally $0.addArrangedSubview(spacer) $0.setCustomSpacing(0, after: spacer) $0.addArrangedSubview(Area(title: "Scale".localized(), control: scaleModePicker)) $0.addArrangedSubview(Area(title: "Size".localized(), control: NSStackView() => { $0.addArrangedSubview(widthField => { $0.snp.makeConstraints{ make in make.width.equalTo(80) } }) $0.addArrangedSubview(NSTextField(labelWithString: "x")) $0.addArrangedSubview(heightField => { $0.snp.makeConstraints{ make in make.width.equalTo(80) } }) })) } override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { if sender.draggingPasteboard.canReadTypes([.URL, .fileURL, .fileContents]) { return .copy } else { return .none } } override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { guard let urls = sender.draggingPasteboard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL], !urls.isEmpty else { return false } self.dragPublisher.send(sender.draggingPasteboard) return true } override func layout() { listView.snp.remakeConstraints{ make in make.height.equalTo(max(200, self.frame.height - 380)) } super.layout() } override func onAwake() { self.registerForDraggedTypes([.URL, .fileURL, .fileContents]) self.addSection( Section(title: "Configuration".localized(), items: [ Area(icon: R.Image.format, title: "Image Format".localized(), control: formatTypePicker), Area(icon: R.Image.paramators, title: "Resize".localized(), control: resizeSwitch), resizeOptionStack ]) ) self.addSection(Section(title: "Converted Images".localized(), items: [listView])) } } final private class ImageListView: NSLoadView { let scrollView = NSScrollView() let listView = NSTableView.list() var convertTasks = [ImageConvertTask]() { didSet { listView.reloadData() } } override func updateLayer() { self.layer?.backgroundColor = R.Color.controlBackgroundColor.cgColor } override func onAwake() { self.wantsLayer = true self.layer?.cornerRadius = R.Size.corner self.addSubview(scrollView) self.scrollView.drawsBackground = false self.scrollView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.scrollView.documentView = listView self.listView.delegate = self self.listView.dataSource = self let menu = NSMenu() menu.addItem(title: "Open in Finder".localized()) { [self] in if let task = convertTasks.at(listView.clickedRow) { NSWorkspace.shared.activateFileViewerSelecting([task.destinationURL]) } } self.listView.menu = menu } } extension ImageListView: NSTableViewDataSource, NSTableViewDelegate { func numberOfRows(in tableView: NSTableView) -> Int { convertTasks.count } func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 46 } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { let cell = ImageListCell() let task = convertTasks[row] cell.imageView.image = task.image cell.titleLabel.stringValue = task.title cell.sizeLabel.stringValue = "\(task.size.width.formattedString()) x \(task.size.height.formattedString())" task.isDone .finally { cell.checkImageView.isHidden = false cell.progressIndicator.isHidden = true } .sink({ cell.checkImageView.image = R.Image.check }, { print($0) cell.checkImageView.image = R.Image.error } ) return cell } } final private class ImageListCell: NSLoadStackView { let imageView = NSImageView() let titleLabel = NSTextField(labelWithString: "Sample.png") => { $0.font = .systemFont(ofSize: R.Size.controlTitleFontSize) $0.lineBreakMode = .byTruncatingTail } let checkImageView = NSImageView() let progressIndicator = NSProgressIndicator() let sizeLabel = NSTextField(labelWithString: "100 x 100") => { $0.font = .systemFont(ofSize: R.Size.controlFontSize) $0.textColor = .secondaryLabelColor $0.lineBreakMode = .byTruncatingTail } override func updateLayer() { imageView.layer?.backgroundColor = NSColor.tertiaryLabelColor.withAlphaComponent(0.05).cgColor } override func onAwake() { self.imageView.wantsLayer = true self.imageView.layer?.cornerRadius = R.Size.corner self.addArrangedSubview(checkImageView) self.checkImageView.isHidden = true self.checkImageView.snp.makeConstraints{ make in make.size.equalTo(16) } self.addArrangedSubview(progressIndicator) self.progressIndicator.style = .spinning self.progressIndicator.startAnimation(nil) self.progressIndicator.snp.makeConstraints{ make in make.size.equalTo(16) } self.addArrangedSubview(imageView) self.spacing = 16 self.edgeInsets = .init(x: 16, y: 4) self.imageView.snp.makeConstraints{ make in make.width.equalTo(48) make.height.equalTo(28) } let titleStack = NSStackView() self.addArrangedSubview(titleStack) titleStack.orientation = .vertical titleStack.alignment = .left titleStack.spacing = 4 titleStack.distribution = .fillProportionally titleStack.addArrangedSubview(titleLabel) titleStack.addArrangedSubview(sizeLabel) titleStack.snp.makeConstraints{ make in make.right.equalToSuperview().inset(16) } } } private let formatter = NumberFormatter() => { $0.maximumFractionDigits = 2 } extension CGFloat { public func formattedString() -> String { formatter.string(from: NSNumber(value: native)) ?? "0" } } extension Double { public func formattedString() -> String { formatter.string(from: NSNumber(value: self)) ?? "0" } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Image Optimizer/ImageOptimaizerView.swift ================================================ // // Image Optimizer.swift // DevToys // // Created by yuki on 2022/02/01. // import CoreUtil final class ImageOptimizerViewController: NSViewController { private let cell = ImageOptimizerView() @Observable var tasks = [ImageOptimizeTask]() @RestorableState("imop.level") var level = OptimizeLevel.medium @RestorableState("imop.override") var overrideSource = false override func loadView() { self.view = cell } override func viewDidLoad() { self.cell.urlPublisher .sink{[unowned self] in processFiles($0) }.store(in: &objectBag) self.cell.deletePublisher .sink{[unowned self] in deleteResult($0) }.store(in: &objectBag) self.cell.levelPicker.itemPublisher .sink{[unowned self] in self.level = $0 }.store(in: &objectBag) self.cell.overrideSwiftch.isOnPublisher .sink{[unowned self] in self.overrideSource = $0 }.store(in: &objectBag) self.$tasks .sink{[unowned self] in self.cell.tasks = $0 }.store(in: &objectBag) self.$level .sink{[unowned self] in self.cell.levelPicker.selectedItem = $0 }.store(in: &objectBag) self.$overrideSource .sink{[unowned self] in self.cell.overrideSwiftch.isOn = $0 }.store(in: &objectBag) self.cell.listView.menu = NSMenu() => { menu in menu.addItem(title: "Open in Finder".localized()) { [self] in if let task = tasks.at(cell.listView.clickedRow) { NSWorkspace.shared.activateFileViewerSelecting([task.url]) } } } } private func deleteResult(_ indexes: [Int]) { indexes.reversed().forEach{ if case .pending = self.tasks[$0].result.state {} else { self.tasks.remove(at: $0) } } } private func processFiles(_ urls: [URL]) { do { self.tasks.append(contentsOf: try urls.compactMap{ try ImageOptimizer.optimize($0, override: overrideSource, optimizeLevel: level) }) } catch ImageOptimizeError.noAccessToDirectory(let url) { Toast(message: "No access to directory '\(url.lastPathComponent)'", color: .systemRed).show() } catch ImageOptimizeError.unkownType(let fileExtension) { Toast(message: "Unsupported file type '\(fileExtension)'", color: .systemRed).show() } catch { print(error) Toast(message: "Unkown Error", color: .systemRed).show() } } } final private class ImageOptimizerView: Page { let overrideSwiftch = NSSwitch() let listView = ImageListView.list() let listScrollView = NSScrollView() let urlPublisher = PassthroughSubject<[URL], Never>() let deletePublisher = PassthroughSubject<[Int], Never>() var tasks: [ImageOptimizeTask] = [] { didSet { listView.reloadData() self.subviews.forEach{ $0.needsLayout = true } } } let levelPicker = EnumPopupButton() override func keyDown(with event: NSEvent) { switch event.hotKey { case .delete: deletePublisher.send(self.listView.selectedRowIndexes.map{ $0 }) default: super.keyDown(with: event) } } override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { if sender.draggingPasteboard.canReadTypes([.URL, .fileURL, .fileContents]) { return .copy } return .none } override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { guard let urls = sender.draggingPasteboard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL] else { return false } urlPublisher.send(urls) return true } override func layout() { listScrollView.snp.remakeConstraints{ make in make.height.equalTo(max(200, self.frame.height - 260)) } super.layout() } override func onAwake() { self.registerForDraggedTypes([.URL, .fileURL, .fileContents]) self.addSection(Section(title: "Configuration".localized(), items: [ Area(icon: R.Image.paramators, title: "Optimize Level".localized(), control: levelPicker), Area(icon: R.Image.export, title: "Override original file".localized(), control: overrideSwiftch) ])) self.listScrollView.documentView = listView self.addSection(Section(title: "Images".localized(), items: [listScrollView])) self.listView.snp.makeConstraints{ make in make.height.greaterThanOrEqualTo(1) // AppKitのバグ対処用 } self.listView.allowsMultipleSelection = true self.listView.delegate = self self.listView.dataSource = self } } extension ImageOptimizerView: NSTableViewDataSource, NSTableViewDelegate { func numberOfRows(in tableView: NSTableView) -> Int { tasks.count } func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 32 } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { let cell = ImageOptimizeCell() let task = tasks[row] cell.titleLabel.stringValue = task.title cell.doneCheckView.isHidden = true cell.indicator.isHidden = false task.result .receive(on: .main) .sink({ cell.doneCheckView.isHidden = false cell.indicator.isHidden = true cell.resultLabel.stringValue = $0 }, { cell.doneCheckView.isHidden = false cell.indicator.isHidden = true cell.resultLabel.stringValue = "\($0)" cell.resultLabel.textColor = .red }) return cell } } final private class ImageListView: NSLoadTableView { private let backgroundLayer = ControlBackgroundLayer.animationDisabled() override func layout() { super.layout() self.backgroundLayer.frame = bounds } override func updateLayer() { self.backgroundLayer.update() } override func onAwake() { self.wantsLayer = true self.layer?.addSublayer(backgroundLayer) } } final private class ImageOptimizeCell: NSLoadView { let titleLabel = NSTextField(labelWithString: "Title.png") let resultLabel = NSTextField(labelWithString: "Optimizing...") let indicator = NSProgressIndicator() let stackView = NSStackView() let doneCheckView = NSImageView() override func onAwake() { self.addSubview(stackView) self.stackView.edgeInsets = .init(x: 8, y: 8) self.stackView.distribution = .fillProportionally self.stackView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.stackView.addArrangedSubview(doneCheckView) self.doneCheckView.isHidden = true self.doneCheckView.image = R.Image.check self.doneCheckView.snp.makeConstraints{ make in make.size.equalTo(16) } self.stackView.addArrangedSubview(indicator) self.indicator.style = .spinning self.indicator.startAnimation(nil) self.indicator.snp.makeConstraints{ make in make.size.equalTo(14) } self.stackView.addArrangedSubview(titleLabel) self.titleLabel.font = .systemFont(ofSize: R.Size.controlTitleFontSize) self.stackView.addArrangedSubview(resultLabel) self.resultLabel.alignment = .right self.resultLabel.font = .systemFont(ofSize: R.Size.controlTitleFontSize) } } extension NSPasteboard { func canReadTypes(_ types: [PasteboardType]) -> Bool { self.canReadItem(withDataConformingToTypes: types.map{ $0.rawValue }) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/Image Optimizer/ImageOptimizer.swift ================================================ // // ImageOptimizer.swift // DevToys // // Created by yuki on 2022/02/02. // import CoreUtil struct ImageOptimizeTask { let title: String let url: URL let result: Promise } enum ImageOptimizeError: Error { case noAccessToDirectory(URL) case unkownType(fileExtension: String) } enum OptimizeLevel: String, TextItem { case low = "Low" case medium = "Medium" case high = "High" case veryHigh = "Very High (Slow)" var title: String { rawValue.localized() } } enum ImageOptimizer { static func optimize(_ url: URL, override: Bool, optimizeLevel: OptimizeLevel) throws -> ImageOptimizeTask { let ext = url.pathExtension.lowercased() if ext == "png" { return try PNGOptimizer.optimize(url, override: override, optimizeLevel: optimizeLevel) } if ext == "jpg" || ext == "jpeg" { return try JPEGOptimizer.optimize(url, override: override, optimizeLevel: optimizeLevel) } throw ImageOptimizeError.unkownType(fileExtension: ext) } } enum PNGOptimizer { private static let optpingURL = Bundle.current.url(forResource: "optipng", withExtension: nil)! static func optimize(_ url: URL, override: Bool, optimizeLevel: OptimizeLevel) throws -> ImageOptimizeTask { var arguments = [String]() switch optimizeLevel { case .low: arguments.append("-o1") case .medium: arguments.append("-o2") case .high: arguments.append("-o3") case .veryHigh: arguments.append("-o7") } if !override { let destinationURL = FileConflictAvoider.avoidConflict(url) arguments.append(contentsOf: ["-out", destinationURL.path]) } arguments.append(url.path) let fileCompression = FileCompression(url: url) let promise = Terminal.run(optpingURL, arguments: arguments) .map{ _ in fileCompression.currentCompressionRatioString() } return ImageOptimizeTask(title: url.lastPathComponent, url: url, result: promise) } } enum JPEGOptimizer { private static let jpegoptimURL = Bundle.current.url(forResource: "jpegoptim", withExtension: nil)! static func optimize(_ url: URL, override: Bool, optimizeLevel: OptimizeLevel) throws -> ImageOptimizeTask { let directoryURL = url.deletingLastPathComponent() guard FileAccessChecker.becomeAccessable(to: directoryURL) else { throw ImageOptimizeError.noAccessToDirectory(directoryURL) } var arguments = [String]() switch optimizeLevel { case .low: arguments.append(contentsOf: ["--strip-all"]) case .medium: arguments.append(contentsOf: ["--strip-all", "-m95"]) case .high: arguments.append(contentsOf: ["--strip-all", "-m90"]) case .veryHigh: arguments.append(contentsOf: ["--strip-all", "-m80"]) } if !override { let destinationURL = FileConflictAvoider.avoidConflict(url) arguments.append(contentsOf: ["--dest", destinationURL.path]) } arguments.append(url.path) let fileCompression = FileCompression(url: url) let promise = Terminal.run(jpegoptimURL, arguments: arguments) .map{ _ in fileCompression.currentCompressionRatioString() } return ImageOptimizeTask(title: url.lastPathComponent, url: url, result: promise) } } extension String: Error {} final class FileCompression { let initialSize: Double? let url: URL init(url: URL) { self.url = url self.initialSize = try? FileManager.default.attributesOfItem(atPath: url.path)[.size] as? Double } private static let numberFormatter = NumberFormatter() => { $0.maximumFractionDigits = 2 } func currentCompressionRatioString() -> String { let currentSize = try? FileManager.default.attributesOfItem(atPath: url.path)[.size] as? Double guard let initialSize = initialSize, let currentSize = currentSize, initialSize != 0 else { return "-%" } let percent = currentSize / initialSize * 100 guard let formattedString = FileCompression.numberFormatter.string(from: NSNumber(value: percent)) else { return "-%" } return formattedString + "%" } } enum FileAccessChecker { static func becomeAccessable(to directoryURL: URL) -> Bool { if hasAccess(to: directoryURL) { return true } let panel = NSOpenPanel() panel.message = "Select Open to allow access to '\(directoryURL.lastPathComponent)'" panel.directoryURL = directoryURL panel.canChooseDirectories = true panel.canChooseFiles = false panel.allowsMultipleSelection = false panel.allowsOtherFileTypes = false guard panel.runModal() == .OK else { return false } guard panel.url == directoryURL else { return false } return true } static func hasAccess(to directoryURL: URL) -> Bool { do { let url = directoryURL.deletingLastPathComponent().appendingPathComponent(".devtoys_mac_write_test") try Data().write(to: url) try FileManager.default.removeItem(at: url) return true } catch { return false } } } ================================================ FILE: DevToys/DevToys/Body/Graphic/PDFGeneratorView+.swift ================================================ // // PDFGenerator.swift // DevToys // // Created by yuki on 2022/02/02. // import CoreUtil final class PDFGeneratorViewController: NSViewController { private let cell = PDFGeneratorView() @RestorableData("pdf.input") var images = [ImageItem]() override func loadView() { self.view = cell } override func viewDidLoad() { self.$images .sink{[unowned self] in cell.imageListView.imageItems = $0 }.store(in: &objectBag) self.cell.dragPublisher .sink{[unowned self] in readURLs($0) }.store(in: &objectBag) self.cell.generateButton.actionPublisher .sink{[unowned self] in makePDF(from: self.images.map{ $0.image }) }.store(in: &objectBag) self.cell.imageListView.removePublisher .sink{[unowned self] in $0.reversed().forEach{ self.images.remove(at: $0) } }.store(in: &objectBag) self.cell.imageListView.movePublisher .sink{[unowned self] in self.moveItems($0, to: $1) }.store(in: &objectBag) self.cell.clearButton.actionPublisher .sink{[unowned self] in self.images = [] }.store(in: &objectBag) } private func moveItems(_ fromRows: [Int], to row: Int) { guard !fromRows.isEmpty else { return } let fromMin = fromRows.min()! let fromRows = fromRows.sorted().reversed() var nextImages = self.images var removed = [ImageItem]() for fromRow in fromRows { let item = nextImages.remove(at: fromRow) removed.append(item) } removed.reverse() if row < fromMin { nextImages.insert(contentsOf: removed, at: row) } else { nextImages.insert(contentsOf: removed, at: row - fromRows.count) } self.images = nextImages } private func readURLs(_ pasteboard: NSPasteboard) { let newImageItems = ImageDropper.images(fromPasteboard: pasteboard) guard !newImageItems.isEmpty else { return } images.append(contentsOf: newImageItems) cell.imageListView.contentView.scrollToBottom() } private func makePDF(from images: [NSImage]) { let panel = NSSavePanel() panel.allowedFileTypes = ["pdf"] guard panel.runModal() == .OK, let url = panel.url else { return } let promise = Promise.async { resolve, _ in let pdfData = NSMutableData() var mediaSize = CGRect(x: 0, y: 0, width: 2000, height: 1000) let pdfConsumer = CGDataConsumer(data: pdfData as CFMutableData)! let pdfContext = CGContext(consumer: pdfConsumer, mediaBox: &mediaSize, nil)! for image in images { var rect = CGRect(size: image.size) pdfContext.beginPage(mediaBox: &rect) pdfContext.draw(image.cgImage!, in: rect) pdfContext.endPage() } pdfContext.closePDF() FileManager.default.createFile(atPath: url.path, contents: pdfData as Data, attributes: nil) resolve(()) } .receive(on: .main) promise .peekProgressIndicator("Generating PDF...".localized()) .sinkWithToast({_ in "PDF Exported!".localized() }) } } final private class PDFGeneratorView: Page { let imageListView = ImageListView() let clearButton = SectionButton(title: "Clear".localized(), image: R.Image.clear) let generateButton = Button(title: "Generate PDF".localized()) let dragPublisher = PassthroughSubject() override func layout() { super.layout() self.imageListView.snp.remakeConstraints{ make in make.height.equalTo(max(240, self.frame.height - 120)) } } override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { if sender.draggingPasteboard.canReadTypes([.URL, .fileURL, .fileContents]) { return .copy } else { return .none } } override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { guard let urls = sender.draggingPasteboard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL], !urls.isEmpty else { return false } self.dragPublisher.send(sender.draggingPasteboard) return true } override func onAwake() { self.registerForDraggedTypes([.URL, .fileURL, .fileContents]) self.addSection2( Section(title: "Images".localized(), items: [imageListView], toolbarItems: [clearButton]), Section(title: "Configuration".localized(), items: [ generateButton, ]) ) => { $0.alignment = .top } } } struct ImageItem: Codable { let fileURL: URL var filename: String { fileURL.lastPathComponent } var filenameWithoutExtension: String { fileURL.lastPathComponentWithoutPathExtension } var image: NSImage { imageContainer.image } private let imageContainer: NSImageContainer init(fileURL: URL, image: NSImage) { self.fileURL = fileURL self.imageContainer = .wrap(image) } init?(fileURL: URL) { guard let image = NSImage(contentsOf: fileURL) else { return nil } self.imageContainer = .wrap(image) self.fileURL = fileURL } } extension URL { var lastPathComponentWithoutPathExtension: String { self.deletingPathExtension().lastPathComponent } } final private class ImageListView: NSLoadScrollView { let listView = NSTableView.list() var imageItems = [ImageItem]() { didSet { listView.reloadData() } } var removePublisher = PassthroughSubject<[Int], Never>() var movePublisher = PassthroughSubject<(from: [Int], to: Int), Never>() private let backgroundLayer = ControlBackgroundLayer.animationDisabled() override func keyDown(with event: NSEvent) { switch event.hotKey { case .delete: self.removePublisher.send(listView.selectedRowIndexes.map{ $0 }) default: super.keyDown(with: event) } } override func updateLayer() { self.backgroundLayer.update() } override func layout() { super.layout() self.backgroundLayer.frame = bounds } override func onAwake() { self.drawsBackground = false self.wantsLayer = true self.layer?.addSublayer(backgroundLayer) self.layer?.cornerRadius = R.Size.corner self.documentView = listView self.listView.delegate = self self.listView.allowsMultipleSelection = true self.listView.dataSource = self self.listView.registerForDraggedTypes([.imageItem]) } } extension ImageListView: NSTableViewDelegate, NSTableViewDataSource { private class Cell: NSLoadStackView { let imageView = NSImageView() let titleLabel = NSTextField(labelWithString: "Title") let pageLabel = NSTextField(labelWithString: "Page 1") override func updateLayer() { imageView.layer?.backgroundColor = NSColor.tertiaryLabelColor.withAlphaComponent(0.05).cgColor } override func onAwake() { self.imageView.wantsLayer = true self.imageView.layer?.cornerRadius = R.Size.corner self.spacing = 16 self.edgeInsets = .init(x: 16, y: 4) self.addArrangedSubview(imageView) self.imageView.snp.makeConstraints{ make in make.width.equalTo(100) make.height.equalTo(64) } self.addArrangedSubview(NSStackView() => { $0.orientation = .vertical $0.alignment = .left $0.addArrangedSubview(titleLabel) $0.addArrangedSubview(pageLabel) }) self.titleLabel.font = .systemFont(ofSize: R.Size.controlTitleFontSize) self.titleLabel.lineBreakMode = .byTruncatingTail self.pageLabel.font = .systemFont(ofSize: R.Size.controlTitleFontSize) self.pageLabel.textColor = .secondaryLabelColor self.pageLabel.lineBreakMode = .byTruncatingTail } } func numberOfRows(in tableView: NSTableView) -> Int { self.imageItems.count } func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 80 } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { let cell = Cell() let imageItem = imageItems[row] cell.titleLabel.stringValue = imageItem.filename cell.imageView.image = imageItem.image cell.pageLabel.stringValue = "\("Page".localized()) \(row + 1)" return cell } func tableView(_ tableView: NSTableView, rowActionsForRow row: Int, edge: NSTableView.RowActionEdge) -> [NSTableViewRowAction] { if edge == .trailing { return [.init(style: .destructive, title: "Delete".localized(), handler: { _, row in self.removePublisher.send([row]) })] } return [] } func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? { NSPasteboardItem() => { $0.setPropertyList(row, forType: .imageItem) } } func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation { if dropOperation == .above { return .move } return .none } func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool { guard dropOperation == .above else { return false } guard let fromRow = info.draggingPasteboard.pasteboardItems?.map({ $0.propertyList(forType: .imageItem) }) as? [Int] else { return false } movePublisher.send((fromRow, row)) return true } } extension NSPasteboard.PasteboardType { static let imageItem = NSPasteboard.PasteboardType("com.devtoys.imageitem") } extension NSClipView { func scrollToBottom() { guard let last = self.documentView?.frame.size.height, let scrollView = self.enclosingScrollView else { return } self.scroll(to: [0, last - scrollView.frame.height]) } } ================================================ FILE: DevToys/DevToys/Body/Graphic/QRCodeReaderView+.swift ================================================ // // QRCodeReaderView+.swift // DevToys // // Created by yuki on 2022/02/28. // import CoreUtil import AVFoundation final class QRCodeReaderViewController: NSViewController { private let cell = QRCodeReaderView() @RestorableState("qrcoder.inputtype") var inputType: QRCodeInputType = .photo @Observable var image: CIImage? = nil @Observable var detectedBound: CIQRCodeFeature? @Observable var detectedMessage: String = "" private let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: nil)! private let detectionQueue = DispatchQueue(label: "qr.code.detector", qos: .userInteractive, attributes: .concurrent) private let cameraManager = CameraManager() private var lastUpdate = Date() override func loadView() { self.view = cell } override func viewDidLoad() { self.cell.imageDropView.imagePublisher .sink{[unowned self] in self.image = $0.image.ciImage; updateDetection() }.store(in: &objectBag) self.cell.inputTypePicker.itemPublisher .sink{[unowned self] in self.inputType = $0; updateCameraState() }.store(in: &objectBag) self.cell.inputClearButton.actionPublisher .sink{[unowned self] in self.image = nil; resetDetectionState() }.store(in: &objectBag) self.$image.map{ $0.map{ NSImage(ciImage: $0) } } .sink{[unowned self] in self.cell.imageDropView.image = $0 }.store(in: &objectBag) self.$detectedBound .sink{[unowned self] in self.cell.imageDropView.detectionBounds = $0 }.store(in: &objectBag) self.$detectedMessage .sink{[unowned self] in self.cell.outputTextView.string = $0 }.store(in: &objectBag) self.$inputType .sink{[unowned self] in self.cell.inputTypePicker.selectedItem = $0 }.store(in: &objectBag) } private func updateCameraState() { self.image = nil self.detectedBound = nil self.detectedMessage = "" switch self.inputType { case .photo: self.cameraManager.stopSession() case .camera: self.cameraManager.startSession(self) } } override func viewDidAppear() { if self.inputType == .camera { self.cameraManager.startSession(self) } } override func viewDidDisappear() { self.cameraManager.stopSession() } private func resetDetectionState() { self.detectedBound = nil self.detectedMessage = "" } private func updateDetection() { guard abs(lastUpdate.timeIntervalSinceNow) > 1 else { return } guard let image = image else { resetDetectionState(); return } self.lastUpdate = Date() self.readQRCode(image) .receive(on: .main) .sink{[self] in guard let feature = $0 else { detectedBound = nil; return } self.detectedBound = feature self.detectedMessage = feature.messageString ?? "" } } private func readQRCode(_ ciImage: CIImage) -> Promise { .async(on: detectionQueue){ self.detector.features(in: ciImage).first as? CIQRCodeFeature } } } extension QRCodeReaderViewController: AVCaptureVideoDataOutputSampleBufferDelegate { func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { connection.videoOrientation = .portrait guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } DispatchQueue.main.async { guard self.inputType == .camera else { return } self.image = CIImage(cvPixelBuffer: pixelBuffer).oriented(.upMirrored) self.updateDetection() } } } enum QRCodeInputType: String, TextItem { case photo = "Photo" case camera = "Camera" var title: String { rawValue } } final private class QRCodeReaderView: Page { let inputTypePicker = EnumPopupButton() let inputClearButton = SectionButton(image: R.Image.clear) let imageDropView = QRImageDropView() let outputTextView = TextViewSection(title: "Output".localized(), options: .defaultOutput) override func layout() { super.layout() self.outputTextView.snp.updateConstraints{ make in make.height.equalTo(max(240, self.frame.height - 200)) } } override func onAwake() { self.addSection(Section( title: "Configuration".localized(), items: [ Area(icon: R.Image.paramators, title: "QR Code Input Type", control: inputTypePicker) ])) self.addSection2( Section(title: "Input".localized(), items: [imageDropView], toolbarItems: [inputClearButton]), outputTextView ) self.imageDropView.snp.makeConstraints{ make in make.height.equalTo(320) } } } final private class QRImageDropView: ImageDropView { var detectionBounds: CIQRCodeFeature? = nil { didSet { updateDetectionBounds() } } private func updateDetectionBounds() { guard let image = self.image, let detectionBounds = detectionBounds else { return self.detectedBoundLayer.isHidden = true } self.detectedBoundLayer.isHidden = false let scale = image.size.aspectFitRatio(fitInside: imageView.frame.size) let imageRect = CGRect(center: imageView.bounds.center, size: image.size * scale) let path = CGMutablePath() path.move(to: imageRect.origin + detectionBounds.topLeft * scale) path.addLine(to: imageRect.origin + detectionBounds.bottomLeft * scale) path.addLine(to: imageRect.origin + detectionBounds.bottomRight * scale) path.addLine(to: imageRect.origin + detectionBounds.topRight * scale) path.closeSubpath() detectedBoundLayer.path = path } override func layout() { super.layout() updateDetectionBounds() } private let detectedBoundView = NSView() private let detectedBoundLayer = CAShapeLayer.animationDisabled() override func onAwake() { super.onAwake() self.wantsLayer = true self.imageView.addSubview(detectedBoundView) self.imageView.snp.remakeConstraints{ make in make.top.bottom.equalToSuperview().inset(16) make.centerX.equalToSuperview() } self.detectedBoundView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.detectedBoundView.wantsLayer = true self.detectedBoundLayer.lineWidth = 5 self.detectedBoundLayer.strokeColor = NSColor.yellow.cgColor self.detectedBoundLayer.fillColor = nil self.detectedBoundView.layer?.addSublayer(detectedBoundLayer) } } extension NSImage { var ciImage: CIImage? { self.cgImage.map{ CIImage(cgImage: $0) } } convenience init(ciImage: CIImage) { let rep = NSCIImageRep(ciImage: ciImage) self.init(size: rep.size) self.addRepresentation(rep) } } ================================================ FILE: DevToys/DevToys/Body/HomeView+.swift ================================================ // // HomeViewController+.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil class SearchViewController: HomeViewController { override func chainObjectDidLoad() { self.appModel.$searchQuery.map{ Query($0) } .sink{[unowned self] query in self.tools = appModel.toolManager.allTools().filter{ query.matches(to: $0.title) } } .store(in: &objectBag) } } class HomeViewController: NSViewController { private let scrollView = NSScrollView() private let collectionView = NSCollectionView() var tools: [Tool] = [] { didSet { collectionView.reloadData() } } override func loadView() { self.view = scrollView self.scrollView.documentView = collectionView let flowLayout = LeftAlignedCollectionViewFlowLayout() flowLayout.itemSize = [125, 230] flowLayout.sectionInset = NSEdgeInsets(top: 32, left: 32, bottom: 32, right: 32) flowLayout.minimumInteritemSpacing = 8 self.collectionView.dataSource = self self.collectionView.collectionViewLayout = flowLayout self.collectionView.register(ToolCollectionItem.self, forItemWithIdentifier: ToolCollectionItem.identifier) self.collectionView.addGestureRecognizer(NSClickGestureRecognizer(target: self, action: #selector(onClick))) } @objc private func onClick(_ recognizer: NSGestureRecognizer) { guard let indexPath = collectionView.indexPathForItem(at: recognizer.location(in: collectionView)), let tool = self.tools.at(indexPath.item) else { return } self.appModel.tool = tool } override func chainObjectDidLoad() { self.tools = appModel.toolManager.allTools().filter{ $0.showOnHome } } } extension HomeViewController: NSCollectionViewDataSource { func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { self.tools.count } func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { let item = collectionView.makeItem(withIdentifier: ToolCollectionItem.identifier, for: indexPath) as! ToolCollectionItem let tool = self.tools[indexPath.item] item.cell.icon = tool.icon item.cell.title = tool.title item.cell.toolDescription = tool.toolDescription return item } } final private class ToolCollectionItem: NSCollectionViewItem { static let identifier = NSUserInterfaceItemIdentifier("ToolCollectionItem") let cell = AllToolCell() override func loadView() { self.view = cell } } final private class AllToolCell: NSLoadView { var icon: NSImage? { get { iconView.iconView.image } set { iconView.iconView.image = newValue } } var title: String { get { titleLabel.stringValue } set { titleLabel.stringValue = newValue } } var toolDescription: String { get { descriptionLabel.stringValue } set { descriptionLabel.stringValue = newValue } } private let iconView = AllToolCellIconView() private let iconContainer = NSView() private let labelStack = NSStackView() private let titleLabel = NSTextField(labelWithString: "Hello World") private let descriptionLabel = NSTextField(labelWithString: "Here you can see the next type of development environment.") private let backgroundLayer = CALayer.animationDisabled() private var isMouseInside = false { didSet { needsDisplay = true } } override func updateLayer() { self.backgroundLayer.areAnimationsEnabled = true if isMouseInside { self.backgroundLayer.backgroundColor = NSColor.textColor.withAlphaComponent(0.1).cgColor } else { self.backgroundLayer.backgroundColor = NSColor.textColor.withAlphaComponent(0.05).cgColor } self.backgroundLayer.areAnimationsEnabled = false } override func mouseEntered(with event: NSEvent) { self.isMouseInside = true } override func mouseExited(with event: NSEvent) { self.isMouseInside = false } override func viewDidHide() { self.isMouseInside = false } override func updateTrackingAreas() { self.trackingAreas.forEach(removeTrackingArea(_:)) self.addTrackingRect(bounds, owner: self, userData: nil, assumeInside: true) } override func layout() { super.layout() self.backgroundLayer.frame = bounds } override func onAwake() { self.wantsLayer = true self.layer?.addSublayer(backgroundLayer) self.backgroundLayer.cornerRadius = R.Size.corner self.backgroundLayer.shadowRadius = 2 self.backgroundLayer.shadowOpacity = 0.2 self.backgroundLayer.shadowOffset = [0, 2] self.addSubview(iconContainer) self.iconContainer.snp.makeConstraints{ make in make.right.left.top.equalToSuperview() make.size.equalTo(125) } self.iconContainer.addSubview(iconView) self.iconView.snp.makeConstraints{ make in make.size.equalTo(74) make.center.equalToSuperview() } self.addSubview(labelStack) self.labelStack.orientation = .vertical self.labelStack.spacing = 4 self.labelStack.alignment = .left self.labelStack.snp.makeConstraints{ make in make.right.left.equalToSuperview().inset(10) make.top.equalTo(iconContainer.snp.bottom) } self.labelStack.addArrangedSubview(titleLabel) self.titleLabel.lineBreakMode = .byWordWrapping self.titleLabel.font = .systemFont(ofSize: 10.5, weight: .medium) self.labelStack.addArrangedSubview(descriptionLabel) self.descriptionLabel.lineBreakMode = .byWordWrapping self.descriptionLabel.font = .systemFont(ofSize: 10) self.descriptionLabel.textColor = .secondaryLabelColor } } final private class AllToolCellIconView: NSLoadView { let iconView = NSImageView() private let backgroundLayer = CALayer.animationDisabled() override func layout() { super.layout() self.backgroundLayer.frame = bounds } override func updateLayer() { self.backgroundLayer.backgroundColor = NSColor.quaternaryLabelColor.cgColor } override func onAwake() { self.wantsLayer = true self.layer?.addSublayer(backgroundLayer) self.backgroundLayer.cornerRadius = R.Size.corner self.backgroundLayer.shadowRadius = 2 self.backgroundLayer.shadowOpacity = 0.2 self.backgroundLayer.shadowOffset = [0, 2] self.addSubview(iconView) self.iconView.image = R.Image.Tool.base64Coder self.iconView.contentTintColor = .textColor self.iconView.snp.makeConstraints{ make in make.size.equalTo(40) make.center.equalToSuperview() } } } ================================================ FILE: DevToys/DevToys/Body/Media/Audio Converter/AudioConverter.swift ================================================ // // AudioConverter.swift // DevToys // // Created by yuki on 2022/02/21. // import CoreUtil struct AudioConvertOptions { let thumbsize: CGSize let removeSourceFile: Bool } struct AudioConvertTask { let title: String let sourceURL: URL let destinationURL: URL let fftask: Promise var completePromise: Promise { fftask.flatMap{ $0.complete.replaceError(with: ()) } } } struct AudioConvertGroup { var title: String var formats: [AudioConvertFormat] } struct AudioConvertFormat: Codable { var title: String var ext: String var options: [String] } enum AudioConverter { static func convert(from sourceURL: URL, format: AudioConvertFormat, options: AudioConvertOptions, destinationURL: URL) -> AudioConvertTask { let fftask = FFExecutor.execute(format.options, inputURL: sourceURL, destinationURL: destinationURL) fftask.eraseToError().flatMap{ $0.complete } .peek{ if options.removeSourceFile { NSWorkspace.shared.recycle([sourceURL], completionHandler: nil) NSSound.dragToTrash?.play() } } .catch({_ in }) return AudioConvertTask(title: sourceURL.lastPathComponent, sourceURL: sourceURL, destinationURL: destinationURL, fftask: fftask) } } ================================================ FILE: DevToys/DevToys/Body/Media/Audio Converter/AudioConverterView+.swift ================================================ // // AudioConverter.swift // DevToys // // Created by yuki on 2022/02/21. // import CoreUtil import Quartz final class AudioConverterViewController: NSViewController { private let cell = AudioConverterView() @Observable var tasks = [AudioConvertTask]() @RestorableData("audio.format") var format: AudioConvertFormat = convertGroups[0].formats[1] @RestorableState("audio.removeSource") var removeSource = false override func loadView() { self.view = cell } override func viewDidLoad() { self.$tasks .sink{[unowned self] in cell.listView.convertTasks = $0 }.store(in: &objectBag) self.$format .sink{[unowned self] in cell.formatPicker.selectedMenuTitle = $0.title }.store(in: &objectBag) self.$removeSource .sink{[unowned self] in cell.removeFileSwitch.isOn = $0 }.store(in: &objectBag) self.cell.formatPublisher .sink{[unowned self] in self.format = $0 }.store(in: &objectBag) self.cell.removeFileSwitch.isOnPublisher .sink{[unowned self] in self.removeSource = $0 }.store(in: &objectBag) self.cell.dropPublisher .sink{[unowned self] in handleDrop($0) }.store(in: &objectBag) self.cell.listView.removePublisher .sink{[unowned self] in removeItems($0) }.store(in: &objectBag) self.cell.clearButton.actionPublisher .sink{[unowned self] in clearTasks() }.store(in: &objectBag) } private func clearTasks() { tasks = tasks.filter{ $0.completePromise.state.isSettled } } private func removeItems(_ indexSet: IndexSet) { for index in indexSet.reversed() { if !tasks[index].completePromise.state.isSettled { self.tasks.remove(at: index) } else { NSSound.beep() } } } private func handleDrop(_ urls: [URL]) { let urls = urls.flatMap{ AudioFileScanner.scan($0) } var newTasks = [AudioConvertTask]() for url in urls { let destinationURL = FileConflictAvoider.avoidConflict(url.deletingPathExtension().appendingPathExtension(format.ext)) let options = AudioConvertOptions(thumbsize: AudioConvertTaskCell.thumbnailSize, removeSourceFile: removeSource) let task = AudioConverter.convert(from: url, format: format, options: options, destinationURL: destinationURL) newTasks.append(task) } self.tasks.insert(contentsOf: newTasks, at: 0) } } final private class AudioConverterView: Page { let formatPicker = PopupButton() let removeFileSwitch = NSSwitch() let listView = AudioConvertListView() let clearButton = SectionButton(title: "Clear".localized(), image: R.Image.clear) let formatPublisher = PassthroughSubject() let dropPublisher = PassthroughSubject<[URL], Never>() override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { if sender.draggingPasteboard.canReadTypes([.fileURL]) { return .copy } else { return .none } } override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { guard let urls = sender.draggingPasteboard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL], !urls.isEmpty else { return false } dropPublisher.send(urls) return true } override func layout() { listView.snp.remakeConstraints{ make in make.height.equalTo(max(200, self.frame.height - 270)) } super.layout() } override func onAwake() { self.registerForDraggedTypes([.fileURL]) self.addSection(Section(title: "Configuration".localized(), items: [ Area(icon: R.Image.format, title: "Format".localized(), control: formatPicker), Area(icon: R.Image.paramators, title: "Remove source file".localized(), message: "Whether to delete the source file after exporting a Audio".localized(), control: removeFileSwitch) ])) self.addSection(Section(title: "Files".localized(), items: [ listView ], toolbarItems: [clearButton])) self.registerFormats(convertGroups) } private func registerFormats(_ groups: [AudioConvertGroup]) { for group in groups { let item = NSMenuItem(title: group.title, isSelected: false, isEnabled: true, action: nil) let submenu = NSMenu() for format in group.formats { submenu.addItem(title: format.title, action: { self.formatPublisher.send(format) }) } item.submenu = submenu formatPicker.menuItems.append(item) } } } final private class AudioConvertListView: NSLoadView, QLPreviewPanelDataSource, QLPreviewPanelDelegate { let scrollView = NSScrollView() let listView = EmptyImageTableView.list() let removePublisher = PassthroughSubject() var convertTasks = [AudioConvertTask]() { didSet { listView.reloadData() } } override func updateLayer() { self.layer?.backgroundColor = R.Color.controlBackgroundColor.cgColor } override func keyDown(with event: NSEvent) { switch event.hotKey { case .space: showQuickLook() case .delete: removePublisher.send(listView.selectedRowIndexes) default: super.keyDown(with: event) } } private func showQuickLook() { guard let panel = QLPreviewPanel.shared() else { return } panel.dataSource = self panel.delegate = self panel.makeKeyAndOrderFront(nil) } override func onAwake() { self.wantsLayer = true self.layer?.cornerRadius = R.Size.corner self.addSubview(scrollView) self.scrollView.drawsBackground = false self.scrollView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.scrollView.documentView = listView self.listView.setFileDropEmptyView() self.listView.delegate = self self.listView.dataSource = self self.listView.allowsMultipleSelection = true let menu = NSMenu() menu.addItem(title: "Open in Finder".localized()) { [self] in guard let task = convertTasks.at(listView.clickedRow) else { return } NSWorkspace.shared.activateFileViewerSelecting([task.destinationURL]) } menu.addItem(title: "Delete".localized()) { [self] in if listView.clickedRow >= 0 { removePublisher.send(.init(integer: listView.clickedRow)) } } menu.addItem(title: "Quick Look".localized()) { [self] in self.showQuickLook() } self.listView.menu = menu } func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int { self.listView.selectedRowIndexes.count } func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem { let index = self.listView.selectedRowIndexes.map{ $0 }[index] return convertTasks[index].destinationURL as QLPreviewItem } func previewPanel(_ panel: QLPreviewPanel!, sourceFrameOnScreenFor item: QLPreviewItem!) -> NSRect { guard let index = convertTasks.firstIndex(where: { item.isEqual($0.destinationURL) }) else { return .zero } return listView.convertToScreen(listView.rect(ofRow: index)) } } extension AudioConvertListView: NSTableViewDataSource, NSTableViewDelegate { func numberOfRows(in tableView: NSTableView) -> Int { convertTasks.count } func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 64 } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { let cell = AudioConvertTaskCell() let task = convertTasks[row] cell.titleLabel.stringValue = task.title cell.infoLabel.stringValue = "Starting...".localized() task.fftask .receive(on: .main) .sink{ task in task.$progress .receive(on: DispatchQueue.main, options: nil) .filter{ $0 != 0 } .sink{[unowned cell] in cell.progressView.doubleValue = $0 cell.infoLabel.stringValue = "\(Int($0 * 100))%" } .store(in: &cell.objectBag) task.complete .receive(on: .main) .sink({ cell.infoLabel.stringValue = "Complete".localized() }, { error in cell.infoLabel.textColor = .systemRed cell.infoLabel.stringValue = "Convert Failed".localized() }) } return cell } } final private class AudioConvertTaskCell: NSLoadView { static let thumbnailSize: CGSize = [50, 50] static let thumbnailImageSize: CGSize = thumbnailSize * (NSScreen.main?.backingScaleFactor ?? 1) let thumbnailImageView = NSImageView() let progressView = NSProgressIndicator() let titleLabel = NSTextField(labelWithString: "Title") let infoLabel = NSTextField(labelWithString: "Title") private let stackView = NSStackView() private let titleStackView = NSStackView() override func onAwake() { self.addSubview(stackView) self.stackView.edgeInsets = .init(x: 16, y: 8) self.stackView.distribution = .fillProportionally self.stackView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.stackView.addArrangedSubview(thumbnailImageView) self.thumbnailImageView.isHidden = true self.thumbnailImageView.snp.makeConstraints{ make in make.size.equalTo(AudioConvertTaskCell.thumbnailSize) } self.stackView.addArrangedSubview(titleStackView) self.titleStackView.spacing = 0 self.titleStackView.orientation = .vertical self.titleStackView.alignment = .left self.titleStackView.addArrangedSubview(titleLabel) self.titleLabel.font = .systemFont(ofSize: R.Size.controlTitleFontSize) self.titleStackView.addArrangedSubview(progressView) self.progressView.isIndeterminate = false self.progressView.minValue = 0 self.progressView.maxValue = 1 self.progressView.usesThreadedAnimation = false self.titleStackView.addArrangedSubview(infoLabel) self.infoLabel.font = .systemFont(ofSize: R.Size.controlFontSize) } } private let convertGroups = [ AudioConvertGroup(title: "ACC(M4A)", formats: [ AudioConvertFormat(title: "ACC 44100Hz 64kbps", ext: "m4a", options: ["-ar", "44100", "-b:a", "64k", "-c:v", "copy"]), AudioConvertFormat(title: "ACC 44100Hz 128kbps", ext: "m4a", options: ["-ar", "44100", "-b:a", "128k", "-c:v", "copy"]), AudioConvertFormat(title: "ACC 44100Hz 256bps", ext: "m4a", options: ["-ar", "44100", "-b:a", "256k", "-c:v", "copy"]) ]), AudioConvertGroup(title: "FLAC", formats: [ AudioConvertFormat(title: "FLAC 32000Hz", ext: "flac", options: ["-ar", "32000"]), AudioConvertFormat(title: "FLAC 44100Hz", ext: "flac", options: ["-ar", "44100"]), AudioConvertFormat(title: "FLAC 48000Hz", ext: "flac", options: ["-ar", "48000"]), ]), AudioConvertGroup(title: "M4R", formats: [ AudioConvertFormat(title: "M4R 44100Hz 64kbs", ext: "m4r", options: ["-ar", "44100", "-b:a", "64k"]), AudioConvertFormat(title: "M4R 44100Hz 96kbs", ext: "m4r", options: ["-ar", "44100", "-b:a", "96k"]), AudioConvertFormat(title: "M4R 44100Hz 128kbs", ext: "m4r", options: ["-ar", "44100", "-b:a", "128k"]), AudioConvertFormat(title: "M4R 44100Hz 224kbs", ext: "m4r", options: ["-ar", "44100", "-b:a", "224k"]), AudioConvertFormat(title: "M4R 44100Hz 320kbs", ext: "m4r", options: ["-ar", "44100", "-b:a", "320k"]), ]), AudioConvertGroup(title: "MP3", formats: [ AudioConvertFormat(title: "MP3 44100Hz 64kbs", ext: "mp3", options: ["-ar", "44100", "-b:a", "64k"]), AudioConvertFormat(title: "MP3 44100Hz 96kbs", ext: "mp3", options: ["-ar", "44100", "-b:a", "96k"]), AudioConvertFormat(title: "MP3 44100Hz 128kbs", ext: "mp3", options: ["-ar", "44100", "-b:a", "128k"]), AudioConvertFormat(title: "MP3 44100Hz 224kbs", ext: "mp3", options: ["-ar", "44100", "-b:a", "224k"]), AudioConvertFormat(title: "MP3 44100Hz 256kbs", ext: "mp3", options: ["-ar", "44100", "-b:a", "256k"]), AudioConvertFormat(title: "MP3 44100Hz 320kbs", ext: "mp3", options: ["-ar", "44100", "-b:a", "320k"]), ]), AudioConvertGroup(title: "WAV", formats: [ AudioConvertFormat(title: "WAV 32000Hz", ext: "wav", options: ["-ar", "32000"]), AudioConvertFormat(title: "WAV 44100Hz", ext: "wav", options: ["-ar", "44100"]), AudioConvertFormat(title: "WAV 48000Hz", ext: "wav", options: ["-ar", "48000"]) ]) ] ================================================ FILE: DevToys/DevToys/Body/Media/Audio Converter/AudioFileScanner.swift ================================================ // // FileTreeScanner.swift // DevToys // // Created by yuki on 2022/02/22. // import CoreUtil enum AudioFileScanner { private static var supportedExtensions: Set = [ "mp2", "mp3", "aac", "flac", "wma", "ogg", "ac3", "m4a", "tta", "ape", "wav", "aiff", "aifc", "gsm", "dct", "au", "mp4", "mkv", "mov", "avi", "wmv" ] static func scan(_ url: URL) -> [URL] { var urls = [URL]() func isAudioFile(_ url: URL) -> Bool { supportedExtensions.contains(url.pathExtension.lowercased()) } if isAudioFile(url) { urls.append(url) } guard let enumerator = FileManager.default .enumerator(at: url, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) else { return urls } for case let fileURL as URL in enumerator { guard let resourceValues = try? fileURL.resourceValues(forKeys: [.isRegularFileKey]) else { continue } guard let isRegularFile = resourceValues.isRegularFile, isRegularFile else { continue } if isAudioFile(fileURL) { urls.append(fileURL) } } return urls } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Color.swift ================================================ // // Color.swift // DevToys // // Created by yuki on 2022/02/19. // import CoreUtil struct Color: Codable { var hue: CGFloat var saturation: CGFloat var brightness: CGFloat var alpha: CGFloat var nsColor: NSColor { NSColor(colorSpace: .current, hue: hue, saturation: saturation, brightness: brightness, alpha: alpha) } var cgColor: CGColor { nsColor.cgColor } var rgb: (CGFloat, CGFloat, CGFloat) { let nsColor = self.nsColor return (nsColor.redComponent, nsColor.greenComponent, nsColor.blueComponent) } var cmyk: (cyan: CGFloat, magenta: CGFloat, yellow: CGFloat, black:CGFloat) { let (r, g, b) = self.rgb let k = 1.0 - max(r, g, b) var c = (1.0-r-k) / (1.0-k) var m = (1.0-g-k) / (1.0-k) var y = (1.0-b-k) / (1.0-k) if c.isNaN { c = 0.0 } if m.isNaN { m = 0.0 } if y.isNaN { y = 0.0 } return (cyan: c, magenta: m, yellow: y, black: k) } var hsl: (hue: CGFloat, saturation: CGFloat, lightness: CGFloat) { var (h, s, b) = (hue, saturation, brightness) let l = ((2.0 - s) * b) / 2.0 switch l { case 0.0, 1.0: s = 0.0 case 0.0..<0.5: s = (s * b) / (l * 2.0) default: s = (s * b) / (2.0 - l * 2.0) } return (hue: h, saturation: s, lightness: l) } init(hue: CGFloat, saturation: CGFloat, lightness: CGFloat, alpha: CGFloat = 1.0) { var (h, s, l) = (hue, saturation, lightness) let t = s * ((l < 0.5) ? l : (1.0 - l)) let b = l + t s = (l > 0.0) ? (2.0 * t / b) : 0.0 self.init(hue: h, saturation: s, brightness: b, alpha: alpha) } init(hue: CGFloat, saturation: CGFloat, brightness: CGFloat, alpha: CGFloat) { self.hue = hue self.saturation = saturation self.brightness = brightness self.alpha = alpha } init(nsColor: NSColor) { self.init(hue: nsColor.hueComponent, saturation: nsColor.saturationComponent, brightness: nsColor.brightnessComponent, alpha: nsColor.alphaComponent) } init?(hex3: String, alpha: CGFloat) { if hex3.isEmpty { return nil } guard hex3.count == 3 else { NSSound.beep(); return nil } let scanner = Scanner(string: hex3) var components: UInt64 = 0 guard scanner.scanHexInt64(&components) else { NSSound.beep(); return nil } let r = CGFloat((components & 0xF00) >> 8) / 255.0 let g = CGFloat((components & 0x0F0) >> 4) / 255.0 let b = CGFloat((components & 0x00F) >> 0) / 255.0 self.init(nsColor: NSColor(red: 17 * r, green: 17 * g, blue: 17 * b, alpha: alpha)) } init?(hex6: String, alpha: CGFloat) { guard hex6.count == 6 else { NSSound.beep(); return nil } let scanner = Scanner(string: hex6) var components: UInt64 = 0 guard scanner.scanHexInt64(&components) else { NSSound.beep(); return nil } let r = CGFloat((components & 0xFF0000) >> 16) / 255.0 let g = CGFloat((components & 0x00FF00) >> 8) / 255.0 let b = CGFloat((components & 0x0000FF) >> 0) / 255.0 self.init(nsColor: NSColor(red: r, green: g, blue: b, alpha: alpha)) } init?(hex8: String) { guard hex8.count == 8 else { NSSound.beep(); return nil } let scanner = Scanner(string: hex8) var components: UInt64 = 0 guard scanner.scanHexInt64(&components) else { NSSound.beep(); return nil } let r = CGFloat((components & 0xFF000000) >> 24) / 255.0 let g = CGFloat((components & 0x00FF0000) >> 16) / 255.0 let b = CGFloat((components & 0x0000FF00) >> 8) / 255.0 let a = CGFloat((components & 0x000000FF) >> 0) / 255.0 self.init(nsColor: NSColor(red: r, green: g, blue: b, alpha: a)) } func withAlpha(_ alpha: CGFloat) -> Color { Color(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha.clamped(0...1)) } func withRed(_ value: CGFloat) -> Color { Color(nsColor: NSColor(red: value.clamped(0...1), green: nsColor.greenComponent, blue: nsColor.blueComponent, alpha: alpha)) } func withGreen(_ value: CGFloat) -> Color { Color(nsColor: NSColor(red: nsColor.redComponent, green: value.clamped(0...1), blue: nsColor.blueComponent, alpha: alpha)) } func withBlue(_ value: CGFloat) -> Color { Color(nsColor: NSColor(red: nsColor.redComponent, green: nsColor.greenComponent, blue: value.clamped(0...1), alpha: alpha)) } func withSB(_ saturation: CGFloat, _ brightness: CGFloat) -> Color { Color(hue: hue, saturation: saturation.clamped(0...1), brightness: brightness.clamped(0...1), alpha: alpha) } func withSaturation(_ saturation: CGFloat) -> Color { Color(hue: hue, saturation: saturation.clamped(0...1), brightness: brightness, alpha: alpha) } func withBrightness(_ brightness: CGFloat) -> Color { Color(hue: hue, saturation: saturation, brightness: brightness.clamped(0...1), alpha: alpha) } func withHue(_ hue: CGFloat) -> Color { Color(hue: hue.clamped(0...1), saturation: saturation, brightness: brightness, alpha: alpha) } func withHSLHue(_ hue: CGFloat) -> Color { let (_, s, l) = self.hsl return Color(hue: hue, saturation: s, lightness: l) } func withHSLSaturation(_ saturation: CGFloat) -> Color { let (h, _, l) = self.hsl return Color(hue: h, saturation: saturation, lightness: l) } func withHSLLightness(_ lightness: CGFloat) -> Color { let (h, s, _) = self.hsl return Color(hue: h, saturation: s, lightness: lightness) } } extension Color { static let `default` = Color(hue: 0, saturation: 1, brightness: 1, alpha: 1) } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/ColorPickerView+.swift ================================================ // // ColorPickerView+.swift // DevToys // // Created by yuki on 2022/02/19. // import CoreUtil final class ColorPickerViewController: NSViewController { private let cell = ColorPickerView() @RestorableData("colorpick.color") var color: Color = Color(hue: 0.48, saturation: 0.9, brightness: 0.9, alpha: 1) @RestorableState("colorpick.pickertype") var pickerType: ColorPickerType = .hsbBox @RestorableState("colorpick.copyType") var copyType: ColorCopyType = .webRGBA @Observable var hex3: String? = nil @Observable var hex6: String = "" @Observable var hex8: String = "" @Observable var red: CGFloat = 0 @Observable var green: CGFloat = 0 @Observable var blue: CGFloat = 0 @Observable var cyan: CGFloat = 0 @Observable var magenta: CGFloat = 0 @Observable var yellow: CGFloat = 0 @Observable var key: CGFloat = 0 @Observable var copyValue = "" override func loadView() { self.view = cell } private func updateComponents() { let (red, green, blue) = color.rgb let (cyan, magenta, yellow, key) = color.cmyk self.hex6 = String(format: "%02X%02X%02X", Int(red * 255), Int(green * 255), Int(blue * 255)) self.hex3 = self.makeHex3(hex6: hex6) self.hex8 = String(format: "%02X%02X%02X%02X", Int(red * 255), Int(green * 255), Int(blue * 255), Int(color.alpha * 255)) self.red = round(red * 255) self.green = round(green * 255) self.blue = round(blue * 255) self.cyan = round(cyan * 100) self.magenta = round(magenta * 100) self.yellow = round(yellow * 100) self.key = round(key * 100) switch copyType { case .components: break case .iosUIColor: self.copyValue = "UIColor(red: \(red.cf), green: \(green.cf), blue: \(blue.cf), alpha: \(color.alpha.cf))" case .macNSColor: self.copyValue = "NSColor(red: \(red.cf), green: \(green.cf), blue: \(blue.cf), alpha: \(color.alpha.cf))" case .swiftuiRGBColor: self.copyValue = "Color(red: \(red.cf), green: \(green.cf), blue: \(blue.cf), opacity: \(color.alpha.cf))" case .swiftuiHSBColor: self.copyValue = "Color(hue: \(color.hue.cf), saturation: \(color.saturation.cf), brightness: \(color.brightness.cf), opacity: \(color.alpha.cf))" case .androidARGB: if color.alpha == 1 { self.copyValue = "Color.rgb(\(Int(red * 255)), \(Int(green * 255)), \(Int(blue * 255)))" } else { self.copyValue = "Color.argb(\(Int(color.alpha * 255)), \(Int(red * 255)), \(Int(green * 255)), \(Int(blue * 255)))" } case .androidHEX: if color.alpha == 1 { self.copyValue = "Color.parseColor(\"#\(self.hex6)\")" } else { self.copyValue = "Color.parseColor(\"#\(self.hex8)\")" } case .androidXML: if color.alpha == 1 { self.copyValue = "#\(self.hex6)" } else { self.copyValue = "#\(self.hex8)" } case .webHEX: if color.alpha == 1 { self.copyValue = "#\(self.hex6)" } else { self.copyValue = "#\(self.hex8)" } case .webRGBA: if color.alpha == 1 { self.copyValue = "rgb(\(Int(red * 255)), \(Int(green * 255)), \(Int(blue * 255)))" } else { self.copyValue = "rgba(\(Int(red * 255)), \(Int(green * 255)), \(Int(blue * 255)), \(color.alpha.formattedString()))" } case .webHSLA: let (h, s, l) = color.hsl if color.alpha == 1 { self.copyValue = "hsl(\(Int(h * 360))deg, \(Int(s * 100))%, \(Int(l * 100))%)" } else { self.copyValue = "hsla(\(Int(h * 360))deg, \(Int(s * 100))%, \(Int(l * 100))%, \(color.alpha.formattedString()))" } } } private func makeHex3(hex6: String) -> String? { guard hex6.count == 6 else { return nil } let charactors = hex6.map{ $0 } guard charactors[0] == charactors[1], charactors[2] == charactors[3], charactors[4] == charactors[5] else { return nil } return "\(charactors[0])\(charactors[2])\(charactors[4])" } private func pickColor() { ACPixelPicker().show() .peek{[self] in self.color = $0; updateComponents() } .catchCancel{} } override func viewDidLoad() { self.$hex3.sink{[unowned self] in self.cell.hex3TextField.string = $0 ?? "" }.store(in: &objectBag) self.$hex6.sink{[unowned self] in self.cell.hex6TextField.string = $0 }.store(in: &objectBag) self.$hex8.sink{[unowned self] in self.cell.hex8TextField.string = $0 }.store(in: &objectBag) self.$red.sink{[unowned self] in self.cell.redField.value = $0 }.store(in: &objectBag) self.$green.sink{[unowned self] in self.cell.greenField.value = $0 }.store(in: &objectBag) self.$blue.sink{[unowned self] in self.cell.blueField.value = $0 }.store(in: &objectBag) self.$cyan.sink{[unowned self] in self.cell.cyanField.value = $0 }.store(in: &objectBag) self.$magenta.sink{[unowned self] in self.cell.magentaField.value = $0 }.store(in: &objectBag) self.$yellow.sink{[unowned self] in self.cell.yellowField.value = $0 }.store(in: &objectBag) self.$key.sink{[unowned self] in self.cell.keyField.value = $0 }.store(in: &objectBag) self.$pickerType.sink{[unowned self] in self.cell.pickerTypePicker.selectedItem = $0 }.store(in: &objectBag) self.$copyType.sink{[unowned self] in self.cell.colorCopyTypePicker.selectedItem = $0 }.store(in: &objectBag) self.$copyValue.sink{[unowned self] in self.cell.textCopyField.string = $0 }.store(in: &objectBag) self.$pickerType .map{[unowned self] type -> NSView in switch type { case .hsbBox: return self.cell.boxHSBPicker case .hsbCircle: return self.cell.circleBoxHSBPicker case .hsbCircleAndBars: return self.cell.circleBarsHSBPicker } } .sink{[unowned self] in self.cell.pickerPlaceholder.contentView = $0 } .store(in: &objectBag) self.$copyType .map{[unowned self] type -> NSView in switch type { case .components: return cell.paramatorsStack default: return cell.textCopyField } } .sink{[unowned self] in self.cell.colorCopyPlaceholder.contentView = $0 } .store(in: &objectBag) self.$color .sink{[unowned self] in cell.boxHSBPicker.color = $0 cell.circleBoxHSBPicker.color = $0 cell.circleBarsHSBPicker.color = $0 cell.colorSampleView.color = $0.cgColor cell.alphaField.value = round($0.alpha * 100) } .store(in: &objectBag) self.cell.pickerTypePicker.itemPublisher .sink{[unowned self] in self.pickerType = $0 }.store(in: &objectBag) self.cell.colorCopyTypePicker.itemPublisher .sink{[unowned self] in self.copyType = $0; updateComponents() }.store(in: &objectBag) self.cell.hex3TextField.endEditingStringPublisher .sink{[unowned self] in self.color = Color(hex3: $0, alpha: color.alpha) ?? color; updateComponents() }.store(in: &objectBag) self.cell.hex6TextField.endEditingStringPublisher .sink{[unowned self] in self.color = Color(hex6: $0, alpha: color.alpha) ?? color; updateComponents() }.store(in: &objectBag) self.cell.hex8TextField.endEditingStringPublisher .sink{[unowned self] in self.color = Color(hex8: $0) ?? color; updateComponents() }.store(in: &objectBag) self.cell.boxHSBPicker.colorPublisher.merge(with: cell.circleBoxHSBPicker.colorPublisher, cell.circleBarsHSBPicker.colorPublisher) .sink{[unowned self] in self.color = $0; updateComponents() }.store(in: &objectBag) self.cell.alphaField.publisher .sink{[unowned self] in self.color = self.color.withAlpha($0/100); updateComponents() }.store(in: &objectBag) self.cell.redField.publisher .sink{[unowned self] in self.color = self.color.withRed($0/255); updateComponents() }.store(in: &objectBag) self.cell.greenField.publisher .sink{[unowned self] in self.color = self.color.withGreen($0/255); updateComponents() }.store(in: &objectBag) self.cell.blueField.publisher .sink{[unowned self] in self.color = self.color.withBlue($0/255); updateComponents() }.store(in: &objectBag) self.cell.pixelPickerButton.actionPublisher .sink{[unowned self] in self.pickColor() }.store(in: &objectBag) self.updateComponents() } } enum ColorPickerType: String, TextItem { case hsbBox = "HSB Box" case hsbCircle = "HSB Circle" case hsbCircleAndBars = "HSB Circle and Bars" var title: String { rawValue.localized() } } enum ColorCopyType: String, TextItem { case components = "Components" case iosUIColor = "iOS UIColor" case macNSColor = "mac NSColor" case swiftuiHSBColor = "SwiftUI HSB Color" case swiftuiRGBColor = "SwiftUI RGB Color" case androidARGB = "Android RGB" case androidHEX = "Android HEX" case androidXML = "Android XML" case webHEX = "Web HEX" case webRGBA = "Web RGB" case webHSLA = "Web HSL" var title: String { rawValue } } final private class ColorPickerView: Page { let pickerTypePicker = EnumPopupButton() let boxHSBPicker = BoxHSBColorPicker() let circleBoxHSBPicker = CircleBoxHSBColorPicker() let circleBarsHSBPicker = CircleBarsHSBColorPicker() let pickerPlaceholder = NSPlaceholderView() let colorSampleView = ColorSampleView() let pixelPickerButton = SectionButton(title: "Pick Color", image: R.Image.spuit) let alphaField = NumberField() => { $0.snp.makeConstraints{ make in make.width.equalTo(100) } } let colorCopyTypePicker = EnumPopupButton() let colorCopyPlaceholder = NSPlaceholderView() let hex3TextField = TextField() => { $0.placeholder = "#XXX" } let hex6TextField = TextField() => { $0.placeholder = "#XXXXXX" } let hex8TextField = TextField() => { $0.placeholder = "#XXXXXXXX" } let redField = NumberField() => { $0.snp.makeConstraints{ make in make.width.equalTo(100) } } let greenField = NumberField() => { $0.snp.makeConstraints{ make in make.width.equalTo(100) } } let blueField = NumberField() => { $0.snp.makeConstraints{ make in make.width.equalTo(100) } } let cyanField = NumberField() => { $0.snp.makeConstraints{ make in make.width.equalTo(100) } } let magentaField = NumberField() => { $0.snp.makeConstraints{ make in make.width.equalTo(100) } } let yellowField = NumberField() => { $0.snp.makeConstraints{ make in make.width.equalTo(100) } } let keyField = NumberField() => { $0.snp.makeConstraints{ make in make.width.equalTo(100) } } let hueField = NumberField() => { $0.snp.makeConstraints{ make in make.width.equalTo(100) } } let saturationField = NumberField() => { $0.snp.makeConstraints{ make in make.width.equalTo(100) } } let brightnessField = NumberField() => { $0.snp.makeConstraints{ make in make.width.equalTo(100) } } let textCopyField = TextField() lazy var paramatorsStack = NSStackView() => { paramatorsStack in paramatorsStack.alignment = .top paramatorsStack.addArrangedSubview(Section(title: "RGB", orientation: .vertical, items: [ redField, greenField, blueField ])) paramatorsStack.addArrangedSubview(Section(title: "HSB", orientation: .vertical, items: [ hueField, saturationField, brightnessField ])) paramatorsStack.addArrangedSubview(Section(title: "CMYK", orientation: .vertical, items: [ cyanField, magentaField, yellowField, keyField ])) } override func onAwake() { self.addSection(Area(icon: R.Image.settings, title: "Picker Type".localized(), control: pickerTypePicker)) self.pickerPlaceholder.contentView = circleBarsHSBPicker self.addSection(pickerPlaceholder) let componentsStack = NSStackView() componentsStack.orientation = .horizontal componentsStack.addArrangedSubview(colorSampleView) componentsStack.addArrangedSubview(pixelPickerButton) componentsStack.addArrangedSubview(NSView()) componentsStack.addArrangedSubview(alphaField) self.addSection(componentsStack) self.addSection(Section(title: "Color Hex".localized(), orientation: .horizontal, fillWidth: false, items: [ hex3TextField, hex6TextField, hex8TextField ])) self.addSection(Section(title: "Color Copy".localized(), items: [ Area(icon: R.Image.copy, title: "Color Copy Type".localized(), control: colorCopyTypePicker), self.colorCopyPlaceholder ])) self.textCopyField.font = .monospacedSystemFont(ofSize: R.Size.controlTitleFontSize, weight: .regular) } } extension NumberField { var publisher: AnyPublisher { valuePublisher.map{ CGFloat($0.reduce(self.value.native)) }.eraseToAnyPublisher() } } extension CGFloat { private static let numberFormatter = NumberFormatter() => { $0.maximumFractionDigits = 2 $0.minimumFractionDigits = 2 } fileprivate var cf: String { Self.numberFormatter.string(from: NSNumber(value: native)) ?? "0" } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Components/BoxHSBColorPicker.swift ================================================ // // BoxColorPicker.swift // DevToys // // Created by yuki on 2022/02/19. // import CoreUtil final class BoxHSBColorPicker: NSLoadStackView { var color = Color.default { didSet { self.colorBox.color = color self.hueBar.color = color self.opacityBar.color = color } } var colorPublisher: AnyPublisher { let p1 = colorBox.valuePublisher.map{ self.color.withSB($0.saturation, $0.brightness) } let p2 = hueBar.huePublisher.map{ self.color.withHue($0) } let p3 = opacityBar.opacityPublisher.map{ v in self.color <=> { $0.alpha = v } } return p1.merge(with: p2, p3).eraseToAnyPublisher() } let colorBox = ColorBoxView() let hueBar = HueBarView() let opacityBar = OpacityBarView() override func onAwake() { self.orientation = .horizontal self.spacing = 16 self.addArrangedSubview(colorBox) self.addArrangedSubview(hueBar) self.addArrangedSubview(opacityBar) self.snp.makeConstraints{ make in make.height.equalTo(200) } } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Components/BrightnessBarView.swift ================================================ // // BrightnessBarView.swift // DevToys // // Created by yuki on 2022/02/19. // import CoreUtil final class BrightnessBarView: NSLoadView { var color: Color = .default { didSet { self.updateHandle(); updateColorLayer() } } let lightnessPublisher = PassthroughSubject() private let handleLayer = ColorPickerHandleLayer() private let colorLayer = CAGradientLayer.animationDisabled() override func layout() { super.layout() self.colorLayer.frame = bounds self.updateHandle() } override func mouseDown(with event: NSEvent) { self.sendLocation(event.location(in: self)) } override func mouseDragged(with event: NSEvent) { self.sendLocation(event.location(in: self)) } private func sendLocation(_ location: CGPoint) { self.lightnessPublisher.send((location.y / bounds.height).clamped(0...1)) } private func updateColorLayer() { self.colorLayer.colors = [ NSColor(colorSpace: .current, hue: color.hue, saturation: color.saturation, brightness: 0, alpha: 1).cgColor, NSColor(colorSpace: .current, hue: color.hue, saturation: color.saturation, brightness: 1, alpha: 1).cgColor ] } private func updateHandle() { self.handleLayer.color = color.withAlpha(1).cgColor self.handleLayer.frame.center = [bounds.midX, color.brightness * bounds.height] } override func onAwake() { self.snp.makeConstraints{ make in make.width.equalTo(R.ColorPicker.barWidth) } self.wantsLayer = true self.layer?.addSublayer(colorLayer) self.layer?.addSublayer(handleLayer) self.layer?.cornerRadius = R.Size.corner self.layer?.borderWidth = 1 self.layer?.borderColor = CGColor.black.withAlpha(0.2) } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Components/CircleBarsHSBColorPicker.swift ================================================ // // CircleBarsHSBColorPicker.swift // DevToys // // Created by yuki on 2022/02/19. // import CoreUtil final class CircleBarsHSBColorPicker: NSLoadStackView { var color = Color.default { didSet { self.circleHueBar.color = color self.opacityBar.color = color self.saturationBar.color = color self.lightnessBar.color = color } } var colorPublisher: AnyPublisher { let p2 = opacityBar.opacityPublisher.map{ v in self.color <=> { $0.alpha = v } } let p1 = circleHueBar.huePublisher.map{ self.color.withHue($0) } let p3 = saturationBar.saturationPublisher.map{ self.color.withSaturation($0) } let p4 = lightnessBar.lightnessPublisher.map{ self.color.withBrightness($0) } return p1.merge(with: p2, p3, p4).eraseToAnyPublisher() } let circleHueBar = CircleHueBarView() let saturationBar = SaturationBarView() let lightnessBar = BrightnessBarView() let opacityBar = OpacityBarView() override func onAwake() { self.orientation = .horizontal self.spacing = 16 self.addArrangedSubview(circleHueBar) self.addArrangedSubview(saturationBar) self.addArrangedSubview(lightnessBar) self.addArrangedSubview(opacityBar) self.snp.makeConstraints{ make in make.height.equalTo(250) } } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Components/CircleBoxHSBColorPicker.swift ================================================ // // CircleColorPicker.swift // DevToys // // Created by yuki on 2022/02/19. // import CoreUtil final class CircleBoxHSBColorPicker: NSLoadStackView { var color = Color.default { didSet { self.colorBox.color = color self.circleHueBar.color = color self.opacityBar.color = color } } var colorPublisher: AnyPublisher { let p1 = colorBox.valuePublisher.map{ self.color.withSB($0.saturation, $0.brightness) } let p2 = circleHueBar.huePublisher.map{ self.color.withHue($0) } let p3 = opacityBar.opacityPublisher.map{ v in self.color <=> { $0.alpha = v } } return p1.merge(with: p2, p3).eraseToAnyPublisher() } let colorBox = ColorBoxView() let circleHueBar = CircleHueBarView() let opacityBar = OpacityBarView() override func onAwake() { self.orientation = .horizontal self.spacing = 16 self.addArrangedSubview(circleHueBar) self.circleHueBar.placeholder.contentView = colorBox self.addArrangedSubview(opacityBar) self.snp.makeConstraints{ make in make.height.equalTo(250) } } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Components/CircleHueBarView.swift ================================================ // // CircleHueBarView.swift // DevToys // // Created by yuki on 2022/02/19. // import CoreUtil final class CircleHueBarView: NSLoadView { var color: Color = .default { didSet { self.updateHandle() } } let huePublisher = PassthroughSubject() let placeholder = NSPlaceholderView() private let handleLayer = ColorPickerHandleLayer() private let maskLayer = CAShapeLayer.animationDisabled() private let borderLayer = CAShapeLayer.animationDisabled() private let hueLayer = CAGradientLayer.animationDisabled() override func mouseDown(with event: NSEvent) { self.sendLocation(event.location(in: self)) } override func mouseDragged(with event: NSEvent) { self.sendLocation(event.location(in: self)) } private func sendLocation(_ location: CGPoint) { let delta = bounds.center - location let radius = atan2(delta.y, delta.x) / (2 * .pi) + 0.5 huePublisher.send(radius.clamped(0...1)) } private func updateHandle() { self.handleLayer.color = Color(hue: color.hue, saturation: 1, brightness: 1, alpha: 1).cgColor let radius = (color.hue) * 2 * .pi self.handleLayer.frame.center = bounds.center + [cos(radius), sin(radius)] * (self.bounds.size.convertToPoint()/2 - [R.ColorPicker.barWidth, R.ColorPicker.barWidth]/2) } override func layout() { super.layout() self.hueLayer.frame = bounds self.maskLayer.path = CGPath(ellipseIn: bounds.slimmed(by: R.ColorPicker.barWidth/2), transform: nil) self.borderLayer.path = self.maskLayer.path! .copy(strokingWithWidth: R.ColorPicker.barWidth-1, lineCap: CGLineCap.butt, lineJoin: .miter, miterLimit: .infinity) self.updateHandle() let half = (bounds.width)/2 let delta = half - half/sqrt(2) self.placeholder.frame = bounds.slimmed(by: delta+R.ColorPicker.barWidth) } override func onAwake() { self.wantsLayer = true self.snp.makeConstraints{ make in make.width.equalTo(self.snp.height) } self.layer?.addSublayer(hueLayer) self.layer?.addSublayer(borderLayer) self.layer?.addSublayer(handleLayer) self.hueLayer.colors = hueColors self.hueLayer.startPoint = [0.5, 0.5] self.hueLayer.endPoint = [1, 0.5] self.hueLayer.type = .conic self.hueLayer.mask = maskLayer self.maskLayer.lineWidth = R.ColorPicker.barWidth self.maskLayer.fillColor = nil self.maskLayer.strokeColor = .black self.borderLayer.lineWidth = 1 self.borderLayer.fillColor = nil self.borderLayer.strokeColor = .black.withAlpha(0.2) self.addSubview(placeholder) } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Components/ColorBoxView.swift ================================================ // // ColorBoxView.swift // DevToys // // Created by yuki on 2022/02/19. // import CoreUtil final class ColorBoxView: NSLoadView { let valuePublisher = PassthroughSubject<(saturation: CGFloat, brightness: CGFloat), Never>() var color: Color = .default { didSet { self.updateHandle(); self.updateHue() } } private let hueLayer = CAGradientLayer.animationDisabled() private let handleLayer = ColorPickerHandleLayer() private let brightnessLayer = CAGradientLayer.animationDisabled() override func mouseDown(with event: NSEvent) { self.sendLocation(event.location(in: self)) } override func mouseDragged(with event: NSEvent) { self.sendLocation(event.location(in: self)) } private func sendLocation(_ location: CGPoint) { let sb = location / bounds.size.convertToPoint() self.valuePublisher.send((saturation: sb.x.clamped(0...1), brightness: sb.y.clamped(0...1))) } private func updateHue() { self.hueLayer.colors = [CGColor.white, NSColor(colorSpace: .current, hue: color.hue, saturation: 1, brightness: 1, alpha: 1).cgColor] } private func updateHandle() { let centerX = color.saturation * bounds.width let centerY = color.brightness * bounds.height self.handleLayer.color = color.cgColor.withAlpha(1) self.handleLayer.frame.center = bounds.origin + [centerX, centerY] } override func layout() { super.layout() self.hueLayer.frame = bounds self.brightnessLayer.frame = bounds self.updateHandle() } override func onAwake() { self.wantsLayer = true self.layer?.addSublayer(hueLayer) self.layer?.addSublayer(brightnessLayer) self.layer?.addSublayer(handleLayer) self.layer?.cornerRadius = R.Size.corner self.layer?.borderWidth = 1 self.layer?.borderColor = CGColor.black.withAlpha(0.2) self.hueLayer.startPoint = [0, 0.5] self.hueLayer.endPoint = [1, 0.5] self.brightnessLayer.startPoint = [0.5, 1] self.brightnessLayer.endPoint = [0.5, 0] self.brightnessLayer.colors = [CGColor.clear, CGColor.black] } } extension CGColor { func withAlpha(_ alpha: CGFloat) -> CGColor { self.copy(alpha: alpha)! } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Components/ColorPickerHandleLayer.swift ================================================ // // ColorPickerHandleLayer.swift // DevToys // // Created by yuki on 2022/02/19. // import CoreUtil private let handleSize: CGFloat = 16 final class ColorPickerHandleLayer: CALoadLayer { var color: CGColor = .white { didSet { colorLayer.backgroundColor = color } } private let colorLayer = CALayer.animationDisabled() override func onAwake() { self.frame.size = [handleSize, handleSize] self.areAnimationsEnabled = false self.cornerRadius = handleSize/2 self.borderWidth = 2 self.borderColor = .white self.backgroundColor = R.Color.transparentBackground.cgColor self.shadowOpacity = 0.2 self.shadowOffset = .zero self.shadowRadius = 1 self.addSublayer(colorLayer) let colorLayerRect = bounds.slimmed(by: 1.5) self.colorLayer.frame = colorLayerRect self.colorLayer.cornerRadius = colorLayerRect.height/2 self.colorLayer.backgroundColor = .white } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Components/ColorSampleView.swift ================================================ // // ColorSampleView.swift // DevToys // // Created by yuki on 2022/02/19. // import CoreUtil final class ColorSampleView: NSLoadView { var color: CGColor = .white { didSet { colorLayer.backgroundColor = color } } private let colorLayer = CALayer.animationDisabled() override func layout() { super.layout() self.colorLayer.frame = bounds } override func onAwake() { self.snp.makeConstraints{ make in make.width.equalTo(64) make.height.equalTo(R.Size.controlHeight) } self.wantsLayer = true self.layer?.cornerRadius = R.Size.corner self.layer?.borderWidth = 1 self.layer?.borderColor = NSColor.black.withAlphaComponent(0.2).cgColor self.layer?.backgroundColor = R.Color.transparentBackground.cgColor self.layer?.addSublayer(colorLayer) self.colorLayer.backgroundColor = .white } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Components/HueBarView.swift ================================================ // // HueBarView.swift // DevToys // // Created by yuki on 2022/02/19. // import CoreUtil final class HueBarView: NSLoadView { var color: Color = .default { didSet { self.updateHandle() } } let huePublisher = PassthroughSubject() private let handleLayer = ColorPickerHandleLayer() private let hueLayer = CAGradientLayer.animationDisabled() override func mouseDown(with event: NSEvent) { self.sendLocation(event.location(in: self)) } override func mouseDragged(with event: NSEvent) { self.sendLocation(event.location(in: self)) } private func sendLocation(_ location: CGPoint) { huePublisher.send((location.y / bounds.height).clamped(0...1)) } private func updateHandle() { self.handleLayer.color = Color(hue: color.hue, saturation: 1, brightness: 1, alpha: 1).cgColor self.handleLayer.frame.center = [bounds.midX, color.hue * bounds.height] } override func layout() { super.layout() self.hueLayer.frame = bounds self.updateHandle() } override func onAwake() { self.snp.makeConstraints{ make in make.width.equalTo(R.ColorPicker.barWidth) } self.wantsLayer = true self.layer?.addSublayer(hueLayer) self.layer?.addSublayer(handleLayer) self.layer?.cornerRadius = R.Size.corner self.layer?.borderWidth = 1 self.layer?.borderColor = CGColor.black.withAlpha(0.2) self.hueLayer.colors = hueColors } } let hueColors = stride(from: 0, to: 1, by: 0.01).map{ NSColor(colorSpace: .current, hue: CGFloat($0), saturation: 1, brightness: 1, alpha: 1).cgColor } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Components/OpacityBarView.swift ================================================ // // OpacityBarView.swift // DevToys // // Created by yuki on 2022/02/19. // import CoreUtil final class OpacityBarView: NSLoadView { var color: Color = .default { didSet { self.updateHandle(); updateColorLayer() } } let opacityPublisher = PassthroughSubject() private let handleLayer = ColorPickerHandleLayer() private let colorLayer = CAGradientLayer.animationDisabled() override func layout() { super.layout() self.colorLayer.frame = bounds self.updateHandle() } override func mouseDown(with event: NSEvent) { self.sendLocation(event.location(in: self)) } override func mouseDragged(with event: NSEvent) { self.sendLocation(event.location(in: self)) } private func sendLocation(_ location: CGPoint) { self.opacityPublisher.send((location.y / bounds.height).clamped(0...1)) } private func updateColorLayer() { self.colorLayer.colors = [color.cgColor.withAlpha(0), color.cgColor.withAlpha(1)] } private func updateHandle() { self.handleLayer.color = color.cgColor self.handleLayer.frame.center = [bounds.midX, color.alpha * bounds.height] } override func onAwake() { self.snp.makeConstraints{ make in make.width.equalTo(R.ColorPicker.barWidth) } self.wantsLayer = true self.layer?.addSublayer(colorLayer) self.layer?.addSublayer(handleLayer) self.layer?.cornerRadius = R.Size.corner self.layer?.borderWidth = 1 self.layer?.borderColor = CGColor.black.withAlpha(0.2) self.layer?.backgroundColor = R.Color.transparentBackground.cgColor } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Components/SaturationBarView.swift ================================================ // // SaturationBarView.swift // DevToys // // Created by yuki on 2022/02/19. // import CoreUtil final class SaturationBarView: NSLoadView { var color: Color = .default { didSet { self.updateHandle(); updateColorLayer() } } let saturationPublisher = PassthroughSubject() private let handleLayer = ColorPickerHandleLayer() private let colorLayer = CAGradientLayer.animationDisabled() override func layout() { super.layout() self.colorLayer.frame = bounds self.updateHandle() } override func mouseDown(with event: NSEvent) { self.sendLocation(event.location(in: self)) } override func mouseDragged(with event: NSEvent) { self.sendLocation(event.location(in: self)) } private func sendLocation(_ location: CGPoint) { self.saturationPublisher.send((location.y / bounds.height).clamped(0...1)) } private func updateColorLayer() { self.colorLayer.colors = [ NSColor(colorSpace: .current, hue: color.hue, saturation: 0, brightness: color.brightness, alpha: 1).cgColor, NSColor(colorSpace: .current, hue: color.hue, saturation: 1, brightness: color.brightness, alpha: 1).cgColor ] } private func updateHandle() { self.handleLayer.color = color.withAlpha(1).cgColor self.handleLayer.frame.center = [bounds.midX, color.saturation * bounds.height] } override func onAwake() { self.snp.makeConstraints{ make in make.width.equalTo(R.ColorPicker.barWidth) } self.wantsLayer = true self.layer?.addSublayer(colorLayer) self.layer?.addSublayer(handleLayer) self.layer?.cornerRadius = R.Size.corner self.layer?.borderWidth = 1 self.layer?.borderColor = CGColor.black.withAlpha(0.2) } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Pixel Picker/ACOverlayController.swift ================================================ // // PPOverlayController.swift // Pixel Picker // import Cocoa import Carbon.HIToolbox private let magnification = CGFloat(12) class ACOverlayController: NSWindowController { // ================================================================================================== // // MARK: - Outlet - @IBOutlet weak var overlayPanel: ACOverlayPanel! @IBOutlet weak var wrapper: PPOverlayWrapper! @IBOutlet weak var preview: ACOverlayPreview! @IBOutlet weak var infoPanel: ACOverlayPanel! @IBOutlet weak var infoWrapper: NSView! @IBOutlet weak var infoDetailField: NSTextField! // ================================================================================================== // // MARK: - Properties - private var completion: (NSColor) -> Void = {_ in } private static let panelSizeNormal: CGFloat = 150 private static let panelSizeLarge: CGFloat = 300 private var panelSize: CGFloat = ACOverlayController.panelSizeNormal private var lastActiveApp: NSRunningApplication? private var lastMouseLocation = NSEvent.mouseLocation private var lastHighlightedColor: NSColor = NSColor.black private var eventMonitor: Any? private var isEnabled: Bool = false { didSet { if isEnabled { lastMouseLocation = NSEvent.mouseLocation startMonitoringEvents() } else { stopMonitoringEvents() } } } // ================================================================================================== // // MARK: - View Cycle - override func awakeFromNib() { infoDetailField.font = .monospacedSystemFont(ofSize: 11, weight: .regular) infoDetailField.textColor = .white infoWrapper.layer?.backgroundColor = #colorLiteral(red: 0.1315930784, green: 0.1315930784, blue: 0.1315930784, alpha: 1) infoWrapper.wantsLayer = true infoWrapper.layer?.cornerRadius = R.Size.corner infoWrapper.layer?.backgroundColor = NSColor.black.cgColor eventMonitor = NSEvent.addLocalMonitorForEvents(matching: [.keyDown]) {[weak self] in guard let self = self else { return nil } if self.isEnabled { self.keyDown(with: $0) } return nil } } private var globalMonitors: [Any] = [] private func startMonitoringEvents() { stopMonitoringEvents() globalMonitors.append(NSEvent.addGlobalMonitorForEvents(matching: [.flagsChanged]) { self.flagsChanged(with: $0) }!) globalMonitors.append(NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved]) { self.mouseMoved(with: $0) }!) } private func stopMonitoringEvents() { while globalMonitors.count > 0 { NSEvent.removeMonitor(globalMonitors.popLast()!) } } // ================================================================================================== // // MARK: - Control - override func mouseDown(with event: NSEvent) { hidePicker() pickColor() } override func keyDown(with event: NSEvent) { switch Int(event.keyCode) { case kVK_Escape: hidePicker() case kVK_Space: hidePicker() pickColor() default: break } } override func mouseMoved(with event: NSEvent) { if isEnabled { let currentMouseLocation = NSEvent.mouseLocation let nextMouseLocation = currentMouseLocation updatePreview(aroundPoint: nextMouseLocation) lastMouseLocation = nextMouseLocation } } // ================================================================================================== // // MARK: - Privtaes - private func moveByPixel(dx: CGFloat, dy: CGFloat) { var mouseLoc = lastMouseLocation let currentScreen = getScreenWithMouse() guard let screenFrame = currentScreen?.frame, let screenScale = currentScreen?.backingScaleFactor else { return } mouseLoc.x += dx / screenScale mouseLoc.y += dy / screenScale lastMouseLocation = mouseLoc updatePreview(aroundPoint: mouseLoc) mouseLoc.y = screenFrame.maxY - mouseLoc.y CGDisplayMoveCursorToPoint(0, mouseLoc) } func pickColor() { completion(lastHighlightedColor) } func showPicker(_ completion: @escaping (NSColor) -> Void) { self.completion = completion if overlayPanel.isVisible || infoPanel.isVisible { return } isEnabled = true overlayPanel.activate(withSize: panelSize, infoPanel: infoPanel) if NSWorkspace.shared.frontmostApplication?.bundleIdentifier != Bundle.main.bundleIdentifier { lastActiveApp = NSWorkspace.shared.frontmostApplication } wrapper.layer?.cornerRadius = panelSize / 2 updatePreview(aroundPoint: NSEvent.mouseLocation) hideCursor() } private func hidePicker() { isEnabled = false showCursor() self.overlayPanel.orderOut(self) self.infoPanel.orderOut(self) if self.lastActiveApp != nil, self.lastActiveApp != NSRunningApplication.current { self.lastActiveApp!.activate(options: .activateAllWindows) self.lastActiveApp = nil } self.close() if let eventMonitor = eventMonitor { NSEvent.removeMonitor(eventMonitor) } } private func getScreenWithMouse() -> NSScreen? { let mouseLocation = NSEvent.mouseLocation let screens = NSScreen.screens let screenWithMouse = screens.first { NSMouseInRect(mouseLocation, $0.frame, false) } return screenWithMouse } private func updateInfoPanel(_ color: NSColor) { infoDetailField.stringValue = color.hexColorCode() } private func getScreenShot(aroundPoint point: NSPoint) -> CGImage? { let cgPoint = convertToCGCoordinateSystem(point) let rect = CGRect(x: cgPoint.x - panelSize / 2, y: cgPoint.y - panelSize / 2, width: panelSize, height: panelSize) return CGWindowListCreateImage(rect, [.optionOnScreenBelowWindow], CGWindowID(overlayPanel.windowNumber), .bestResolution) } private func updatePreview(aroundPoint point: NSPoint) { if !overlayPanel.isKeyWindow { overlayPanel.activate(withSize: panelSize, infoPanel: infoPanel) showCursor() hideCursor() } overlayPanel.setFrameOrigin(NSPoint(x: point.x - (panelSize / 2), y: point.y - (panelSize / 2))) let normalisedPoint = NSPoint(x: round(point.x * 2) / 2, y: round(point.y * 2) / 2) guard let screenShot = getScreenShot(aroundPoint: normalisedPoint) else { return } let zoomReciprocal: CGFloat = 1.0 / magnification let currentSize = CGFloat(screenShot.width) + 1 let origin = floor(currentSize * ((1 - zoomReciprocal) / 2)) let x = origin + (isHalf(normalisedPoint.x) ? 1 : 0) let y = origin + (isHalf(normalisedPoint.y) ? 1 : 0) let zoomedSize = floor(ensureOdd(currentSize * zoomReciprocal)) let croppedRect = CGRect(x: x, y: y, width: zoomedSize, height: zoomedSize) let zoomedImage: CGImage = screenShot.cropping(to: croppedRect)! let pixelSize = panelSize / zoomedSize return updatePreview(zoomedImage, pixelSize, zoomedSize) } private func updatePreview(_ image: CGImage, _ pixelSize: CGFloat, _ numberOfPixels: CGFloat) { let middlePosition = numberOfPixels / 2 let colorAtPixel = image.colorAt(x: Int(middlePosition), y: Int(middlePosition)) let contrastingColor = colorAtPixel.bestContrastingColor() updateInfoPanel(colorAtPixel) preview.layer?.contents = image preview.updateCrosshair(pixelSize, middlePosition, contrastingColor.cgColor) preview.updateGrid(cellSize: pixelSize, numberOfCells: Int(numberOfPixels), shouldDisplay: true) lastHighlightedColor = colorAtPixel } } extension NSColor { public func hexColorCode() -> String { String(format: "%02X%02X%02X", Int(round(self.redComponent * 255)), Int(round(self.greenComponent * 255)), Int(round(self.blueComponent * 255))) } public convenience init?(colorSpace: NSColorSpace, hexColorCode: String) { guard hexColorCode.count == 6 else { return nil } let scanner = Scanner(string: hexColorCode) var value = UInt64.zero scanner.scanHexInt64(&value) let r = CGFloat((value & 0xFF0000) >> 16) / 255 let g = CGFloat((value & 0x00FF00) >> 8) / 255 let b = CGFloat((value & 0x0000FF) >> 0) / 255 self.init(colorSpace: colorSpace, red: r, green: g, blue: b, alpha: 1) } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Pixel Picker/ACOverlayController.xib ================================================ ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Pixel Picker/ACOverlayPanel.swift ================================================ // // ACOverlayPanel.swift // PixelPicker // // Created by yuki on 2020/06/30. // Copyright © 2020 yuki. All rights reserved. // import Cocoa class ACOverlayPanel: NSPanel { override var canBecomeKey: Bool { true } override var canBecomeMain: Bool { true } override func awakeFromNib() { self.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] self.level = .popUpMenu self.styleMask = .nonactivatingPanel self.isOpaque = false self.backgroundColor = NSColor.clear self.acceptsMouseMovedEvents = true } func activate(withSize size: CGFloat, infoPanel: ACOverlayPanel) { makeKeyAndOrderFront(self) setFrame(NSRect(x: frame.origin.x, y: frame.origin.y, width: size + 1, height: size + 1), display: false) addChildWindow(infoPanel, ordered: .above) let origin = NSPoint(x: frame.midX - infoPanel.frame.width / 2, y: frame.midY - 40) infoPanel.setFrameOrigin(origin) } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Pixel Picker/ACOverlayPreview.swift ================================================ // // ACOverlayPreview.swift // PixelPicker // // Created by yuki on 2020/06/30. // Copyright © 2020 yuki. All rights reserved. // import Cocoa class ACOverlayPreview: NSView, CALayerDelegate { private var grid: CAShapeLayer = CAShapeLayer() private var lastCellSize: CGFloat? private var lastNumberOfCells: Int? var crosshair: CAShapeLayer = CAShapeLayer() override var wantsUpdateLayer: Bool { get { return true } } override func awakeFromNib() { wantsLayer = true layer?.magnificationFilter = .nearest layer?.contentsGravity = .resizeAspectFill layer?.delegate = self // Add the grid shape layers to the view. grid.strokeColor = NSColor.lightGray.cgColor grid.fillColor = nil layer?.addSublayer(grid) // Prepare crosshair shape layers. crosshair.strokeColor = NSColor.black.cgColor crosshair.fillColor = nil layer?.addSublayer(crosshair) } // Update the crosshair with the correct color, position and size. func updateCrosshair(_ pixelSize: CGFloat, _ middle: CGFloat, _ color: CGColor) { let pos: CGFloat = (pixelSize * middle) - (pixelSize / 2) + 0.5 let pixelRect = NSRect(x: pos, y: pos, width: pixelSize, height: pixelSize) crosshair.path = CGPath(rect: pixelRect, transform: nil) crosshair.strokeColor = color setNeedsDisplay(pixelRect) } // Redraws the grid in the preview. func updateGrid(cellSize size: CGFloat, numberOfCells n: Int, shouldDisplay: Bool) { // Disable implicit animations within this transaction. CATransaction.setDisableActions(true) grid.opacity = 0.25 // Only redraw the grid if we need to display it and the dimensions have changed since last // the last time we drew it. guard shouldDisplay && (size != lastCellSize || n != lastNumberOfCells) else { return } lastCellSize = size lastNumberOfCells = n let path = NSBezierPath() let start = CGFloat(0) + 0.5 let end = CGFloat(n) * size + 0.5 for i in 1.. Void) { controller.showPicker { completion(Color(nsColor: $0)) } } } extension ACPixelPicker { func show() -> Promise { Promise{ resolve, reject in self.show{ if let color = $0 { resolve(color) } else { reject(.shared) } } } } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Pixel Picker/Utils/Ex+CGImage.swift ================================================ // // CGImage.swift // Pixel Picker // import Cocoa extension CGImage { func colorAt(x: Int, y: Int) -> NSColor { assert(0 <= x && x < self.width) assert(0 <= y && y < self.height) let bitmapBytesPerRow = width * 4 let bitmapByteCount = bitmapBytesPerRow * Int(self.height) // Allocate memory for image data. This is the destination in memory where any drawing to // the bitmap context will be rendered. let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue) let bitmapData = malloc(bitmapByteCount) // Since we manually allocate memeory for the data, we must ensure that the same memory is // freed after we've used it. defer { free(bitmapData) } // Create the bitmap context. let context = CGContext( data: bitmapData, width: self.width, height: self.height, bitsPerComponent: 8, bytesPerRow: bitmapBytesPerRow, space: .current, bitmapInfo: bitmapInfo.rawValue ) // Extract the pixel data from the right offset. if context != nil { // First, we draw the image data onto the context we created. let rect = CGRect(x: 0, y: 0, width: self.width, height: self.height) context!.draw(self, in: rect) // Then we extract the data at the right spot. let data = context!.data! let offset = 4 * (y * width + x) let a = CGFloat(data.load(fromByteOffset: offset, as: UInt8.self)) / 255.0 let r = CGFloat(data.load(fromByteOffset: offset + 1, as: UInt8.self)) / 255.0 let g = CGFloat(data.load(fromByteOffset: offset + 2, as: UInt8.self)) / 255.0 let b = CGFloat(data.load(fromByteOffset: offset + 3, as: UInt8.self)) / 255.0 return NSColor(red: r, green: g, blue: b, alpha: a) } // Creating the context failed, so return a default color instead. // Hopefully, this should never happen. return NSColor.black } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Pixel Picker/Utils/Ex+NSBezierPath.swift ================================================ // // NSBezierPath.swift // Pixel Picker // import Cocoa extension NSBezierPath { var cgPath: CGPath { let path = CGMutablePath() var points = [CGPoint](repeating: .zero, count: 3) for i in 0 ..< self.elementCount { switch self.element(at: i, associatedPoints: &points) { case .moveTo: path.move(to: points[0]) case .lineTo: path.addLine(to: points[0]) case .curveTo: path.addCurve(to: points[2], control1: points[0], control2: points[1]) case .closePath: path.closeSubpath() @unknown default: fatalError() } } return path } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Pixel Picker/Utils/Ex+NSColor.swift ================================================ // // NSColor.swift // Pixel Picker // import Cocoa extension NSColor { func image(withSize size: NSSize) -> NSImage { let image = NSImage(size: size) image.lockFocus() self.set() NSRect(origin: NSPoint.zero, size: size).fill() image.unlockFocus() return image } func bestContrastingColor() -> NSColor { let rgb: [CGFloat] = [self.redComponent, self.greenComponent, self.blueComponent].map({ if $0 <= 0.03928 { return $0 / 12.92 } else { return pow(($0 + 0.055) / 1.055, 2.4) } }) let L = 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2] return L > 0.197 ? NSColor.black : NSColor.white } } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Pixel Picker/Utils/ShowAndHideCursor.h ================================================ // // ShowAndHideCursor.h // Pixel Picker // #ifndef ShowAndHideCursor_h #define ShowAndHideCursor_h #import #import CGDirectDisplayID kCGDirectMainDisplayGetter(void); #endif ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Pixel Picker/Utils/ShowAndHideCursor.m ================================================ // // ShowAndHideCursor.m // Pixel Picker // #include "ShowAndHideCursor.h" // Unfortunately `kCGDirectMainDisplay` is unavailable in Swift. CGDirectDisplayID kCGDirectMainDisplayGetter() { return kCGDirectMainDisplay; } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Pixel Picker/Utils/ShowAndHideCursor.swift ================================================ // // ShowAndHideCursor.swift // Pixel Picker // import Foundation import ApplicationServices typealias notAPrivateAPI0 = @convention(c) () -> CInt typealias notAPrivateAPI1 = @convention(c) (CInt, CInt, CFString, CFTypeRef) -> CGError let unsuspiciousArrayOfIntsThatDoNotObfuscateAnything: [[Int]] = [ [1, 3, 3, 7], [ 84, 123, 118, 120, 106, 115, 54, 84, 114, 108, 125, 109, 127, 135, 62, 86, 131, 115, 128, 121, 140, 133, 137, 131, 140, 73, 92, 140, 141, 138, 136, 131, 130, 150, 140, 147, 147, 121, 140, 154, 159, 147, 142, 145, 160, 92, 149, 162, 146, 159, 152, 171, 164, 168, 162, 103, 122, 170, 171, 168, 166, 161, 160, 180, 170, 177, 177, 151, 170, 184, 189, 177, 172, 175, 190 ], [97, 70, 75, 88, 74, 108, 110, 106, 127, 119, 128, 80, 125, 125, 126, 118, 117, 135, 125, 132, 132], [70, 75, 88, 89, 108, 124, 76, 121, 121, 122, 114, 113, 131, 121, 128, 128, 99, 134, 132, 134, 124, 138, 141, 147] ] func aFnThatDoesNotObfuscateAnythingAtAll(_ i: Int, _ ints: [Int]) -> String { return String(ints.enumerated().map({ Character(UnicodeScalar($0.element + i - $0.offset)!) })) } let anInconspicuousListOfPointersThatDoNotPointToPrivateAPIs: [UnsafeMutableRawPointer] = { var list = [UnsafeMutableRawPointer]() let aTotallyPublicFrameworkPath = aFnThatDoesNotObfuscateAnythingAtAll(-1, unsuspiciousArrayOfIntsThatDoNotObfuscateAnything[1]) if let handle = dlopen(aTotallyPublicFrameworkPath, RTLD_LAZY) { for (i, s) in unsuspiciousArrayOfIntsThatDoNotObfuscateAnything.dropFirst(2).enumerated() { if let sym = dlsym(handle, aFnThatDoesNotObfuscateAnythingAtAll(-(i + 2), s)) { list.append(sym) } } dlclose(handle) } return list }() let kCGDirectMainDisplay = kCGDirectMainDisplayGetter() var cursorIsHidden = false func showCursor() { CGDisplayShowCursor(kCGDirectMainDisplay) } func hideCursor() { let pt0 = anInconspicuousListOfPointersThatDoNotPointToPrivateAPIs[0] let cid = unsafeBitCast(pt0, to: notAPrivateAPI0.self)() let pStr = "SetsCursorInBackground" as CFString let pt1 = anInconspicuousListOfPointersThatDoNotPointToPrivateAPIs[1] _ = unsafeBitCast(pt1, to: notAPrivateAPI1.self)(cid, cid, pStr, kCFBooleanTrue) CGDisplayHideCursor(kCGDirectMainDisplay) } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/Pixel Picker/Utils/Util+PixelPicker.swift ================================================ // // Util.swift // Pixel Picker // import Cocoa let APP_NAME = Bundle.main.infoDictionary![kCFBundleNameKey as String] as! String let APPLE_INTERFACE_STYLE = "AppleInterfaceStyle" // Copies the given string to the clipboard. func copyToPasteboard(stringValue value: String) { NSPasteboard.general.declareTypes([.string], owner: nil) NSPasteboard.general.setString(value, forType: .string) } // Ensure that the given number is odd. func ensureOdd(_ x: CGFloat) -> CGFloat { if Int(x) % 2 == 0 { return x + 1 } return x } // Checks whether a float roughly ends in ".5". func isHalf(_ x: CGFloat) -> Bool { return Int(x * 2) % 2 != 0 } // The Cocoa APIs have a coordinate system (origin is top-left of the screen) but the // Carbon/CoreGraphics APIs use an old coordinate system where the origin is the // bottom-left corner *of the primary display*. // The primary display is always the first item of the NSScreen.screens array. func convertToCGCoordinateSystem(_ point: NSPoint) -> CGPoint { return CGPoint(x: point.x, y: NSScreen.screens[0].frame.size.height - point.y) } // Returns the screen which contains the mouse cursor. func getScreenFromPoint(_ point: NSPoint) -> NSScreen? { return NSScreen.screens.first { NSMouseInRect(point, $0.frame, false) } } // Checks if the given point is outside of the given rect. // Returns which coordinates are outside, if any. enum Coordinate { case x, y, both, none static func isOutsideRect(_ point: NSPoint, _ rect: NSRect) -> Coordinate { let x = point.x < rect.origin.x || point.x > (rect.origin.x + rect.width) let y = point.y < rect.origin.y || point.y > (rect.origin.y + rect.height) if x && y { return .both } if x { return .x } if y { return .y } return .none } } // A simple helper to run animations with the same context configration. func runAnimation(_ f: (NSAnimationContext) -> Void, done: (() -> Void)?) { NSAnimationContext.runAnimationGroup({ context in context.duration = 0.5 context.timingFunction = CAMediaTimingFunction.init(name: .easeInEaseOut) context.allowsImplicitAnimation = true f(context) }, completionHandler: done) } // Make menu bar images templates with 16x16 dimensions. func setupMenuBarIcon(_ image: NSImage?) -> NSImage? { image?.isTemplate = true image?.size = NSSize(width: 16, height: 16) return image } ================================================ FILE: DevToys/DevToys/Body/Media/Color Picker/R+ColorPicker.swift ================================================ // // R+ColorPicker.swift // DevToys // // Created by yuki on 2022/02/19. // extension R { enum ColorPicker { // static let pickerHeight: CGFloat = 200 static let barWidth: CGFloat = 20 } } ================================================ FILE: DevToys/DevToys/Body/Media/Movie to Gif/GifConverter.swift ================================================ // // GifConverter.swift // DevToys // // Created by yuki on 2022/02/20. // import CoreUtil import CoreGraphics struct GifConvertOptions { let thumbsize: CGSize let width: CGFloat let fps: Int let removeSourceFile: Bool } struct GifConvertTask { let thumbnail: NSImage? let title: String let movieURL: URL let destinationURL: URL let fftask: Promise } enum GifConverter { static func convert(_ movieURL: URL, options: GifConvertOptions, to destinationURL: URL) -> Promise { var arguments = [String]() arguments.append("-filter_complex") arguments.append("[0:v] fps=\(options.fps),scale=\(options.width):-1,split [a][b];[a] palettegen [p];[b][p] paletteuse") let fftask = FFExecutor.execute(arguments, inputURL: movieURL, destinationURL: destinationURL) let thumbnail = FFThumnailGenerator.thumbnail(of: movieURL, size: options.thumbsize) fftask.eraseToError().flatMap{ $0.complete } .peek{ if options.removeSourceFile { NSWorkspace.shared.recycle([movieURL], completionHandler: nil) NSSound.dragToTrash?.play() } } .catch({_ in }) return thumbnail.map{ GifConvertTask(thumbnail: $0, title: movieURL.lastPathComponent, movieURL: movieURL, destinationURL: destinationURL, fftask: fftask) } } } extension NSSound { static let dragToTrash = NSSound(contentsOfFile: "/System/Library/Components/CoreAudio.component/Contents/SharedSupport/SystemSounds/dock/drag to trash.aif", byReference: true) } ================================================ FILE: DevToys/DevToys/Body/Media/Movie to Gif/GifConverterView+.swift ================================================ // // MovieToGifView.swift // DevToys // // Created by yuki on 2022/02/20. // import CoreUtil import CoreGraphics import Quartz final class GifConverterViewController: NSViewController { private let cell = GifConverterView() @Observable var tasks = [GifConvertTask]() @RestorableState("gif.width") var width = 640.0 @RestorableState("gif.fps") var fps = 10 @RestorableState("gif.removeSource") var removeSource = false override func loadView() { self.view = cell } override func chainObjectDidLoad() { self.$tasks .sink{[unowned self] in cell.listView.convertTasks = $0 }.store(in: &objectBag) self.$width .sink{[unowned self] in cell.widthField.value = $0 }.store(in: &objectBag) self.$fps .sink{[unowned self] in cell.fpsField.value = CGFloat($0) }.store(in: &objectBag) self.$removeSource .sink{[unowned self] in cell.removeFileSwitch.isOn = $0 }.store(in: &objectBag) self.cell.widthField.publisher .sink{[unowned self] in self.width = $0 }.store(in: &objectBag) self.cell.fpsField.publisher .sink{[unowned self] in self.fps = Int($0) }.store(in: &objectBag) self.cell.removeFileSwitch.isOnPublisher .sink{[unowned self] in self.removeSource = $0 }.store(in: &objectBag) self.cell.dropPublisher .sink{[unowned self] in handleDrop($0) }.store(in: &objectBag) self.cell.listView.removePublisher .sink{[unowned self] in removeItems($0) }.store(in: &objectBag) } private func removeItems(_ indexSet: IndexSet) { for index in indexSet.reversed() { self.tasks.remove(at: index) } } private func handleDrop(_ urls: [URL]) { var newTasks = [Promise]() for url in urls { let destinationURL = url.deletingPathExtension().appendingPathExtension("gif") let options = GifConvertOptions(thumbsize: GifConvertTaskCell.thumbnailImageSize, width: width, fps: fps, removeSourceFile: removeSource) let task = GifConverter.convert(url, options: options, to: destinationURL) newTasks.append(task) } Promise.combineAll(newTasks) .receive(on: .main) .sink{ self.tasks.insert(contentsOf: $0, at: 0) } } } final private class GifConverterView: Page { let fpsField = NumberField(autoWidth: ()) let widthField = NumberField(autoWidth: ()) let removeFileSwitch = NSSwitch() let listView = GifConvertListView() let dropPublisher = PassthroughSubject<[URL], Never>() override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { if sender.draggingPasteboard.canReadTypes([.fileURL]) { return .copy } else { return .none } } override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { guard let urls = sender.draggingPasteboard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL], !urls.isEmpty else { return false } dropPublisher.send(urls) return true } override func layout() { listView.snp.remakeConstraints{ make in make.height.equalTo(max(200, self.frame.height - 330)) } super.layout() } override func onAwake() { self.registerForDraggedTypes([.fileURL]) self.addSection(Section(title: "Configuration".localized(), items: [ Area(icon: R.Image.spacing, title: "Width".localized(), message: "The width of the Gif file".localized(), control: widthField), Area(icon: R.Image.paramators, title: "FPS".localized(), message: "FPS of the Gif file to be exported".localized(), control: fpsField), Area(icon: R.Image.paramators, title: "Remove source file".localized(), message: "Whether to delete the source file after exporting a Gif".localized(), control: removeFileSwitch) ])) self.addSection(Section(title: "Images".localized(), items: [ listView ])) } } final private class GifConvertListView: NSLoadView, QLPreviewPanelDataSource, QLPreviewPanelDelegate { let scrollView = NSScrollView() let listView = EmptyImageTableView.list() => { $0.setFileDropEmptyView() } let removePublisher = PassthroughSubject() var convertTasks = [GifConvertTask]() { didSet { listView.reloadData() } } override func updateLayer() { self.layer?.backgroundColor = R.Color.controlBackgroundColor.cgColor } override func keyDown(with event: NSEvent) { switch event.hotKey { case .space: showQuickLook() case .delete: removePublisher.send(listView.selectedRowIndexes) default: super.keyDown(with: event) } } private func showQuickLook() { guard let panel = QLPreviewPanel.shared() else { return } panel.dataSource = self panel.delegate = self panel.makeKeyAndOrderFront(nil) } override func onAwake() { self.wantsLayer = true self.layer?.cornerRadius = R.Size.corner self.addSubview(scrollView) self.scrollView.drawsBackground = false self.scrollView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.scrollView.documentView = listView self.listView.delegate = self self.listView.dataSource = self self.listView.allowsMultipleSelection = true let menu = NSMenu() menu.addItem(title: "Open in Finder".localized()) { [self] in guard let task = convertTasks.at(listView.clickedRow) else { return } NSWorkspace.shared.activateFileViewerSelecting([task.destinationURL]) } menu.addItem(title: "Delete".localized()) { [self] in if listView.clickedRow >= 0 { removePublisher.send(.init(integer: listView.clickedRow)) } } menu.addItem(title: "Quick Look".localized()) { [self] in self.showQuickLook() } self.listView.menu = menu } func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int { self.listView.selectedRowIndexes.count } func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem { let index = self.listView.selectedRowIndexes.map{ $0 }[index] return convertTasks[index].destinationURL as QLPreviewItem } func previewPanel(_ panel: QLPreviewPanel!, sourceFrameOnScreenFor item: QLPreviewItem!) -> NSRect { guard let index = convertTasks.firstIndex(where: { item.isEqual($0.destinationURL) }) else { return .zero } return listView.convertToScreen(listView.rect(ofRow: index)) } } extension GifConvertListView: NSTableViewDataSource, NSTableViewDelegate { func numberOfRows(in tableView: NSTableView) -> Int { convertTasks.count } func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 64 } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { let cell = GifConvertTaskCell() let task = convertTasks[row] cell.titleLabel.stringValue = task.title cell.infoLabel.stringValue = "Starting...".localized() cell.thumbnailImageView.image = task.thumbnail task.fftask .receive(on: .main) .sink{ task in task.$progress .receive(on: DispatchQueue.main, options: nil) .filter{ $0 != 0 } .sink{[unowned cell] in cell.progressView.doubleValue = $0 cell.infoLabel.stringValue = "\(Int($0 * 100))%" } .store(in: &cell.objectBag) task.complete .receive(on: .main) .sink({ cell.infoLabel.stringValue = "Complete".localized() }, { error in cell.infoLabel.textColor = .systemRed cell.infoLabel.stringValue = "Convert Failed".localized() }) } return cell } } final private class GifConvertTaskCell: NSLoadView { static let thumbnailSize: CGSize = [50, 50] static let thumbnailImageSize: CGSize = thumbnailSize * (NSScreen.main?.backingScaleFactor ?? 1) let thumbnailImageView = NSImageView() let progressView = NSProgressIndicator() let titleLabel = NSTextField(labelWithString: "Title") let infoLabel = NSTextField(labelWithString: "Title") private let stackView = NSStackView() private let titleStackView = NSStackView() override func onAwake() { self.addSubview(stackView) self.stackView.edgeInsets = .init(x: 16, y: 8) self.stackView.distribution = .fillProportionally self.stackView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.stackView.addArrangedSubview(thumbnailImageView) self.thumbnailImageView.snp.makeConstraints{ make in make.size.equalTo(GifConvertTaskCell.thumbnailSize) } self.stackView.addArrangedSubview(titleStackView) self.titleStackView.spacing = 0 self.titleStackView.orientation = .vertical self.titleStackView.alignment = .left self.titleStackView.addArrangedSubview(titleLabel) self.titleLabel.font = .systemFont(ofSize: R.Size.controlTitleFontSize) self.titleStackView.addArrangedSubview(progressView) self.progressView.isIndeterminate = false self.progressView.minValue = 0 self.progressView.maxValue = 1 self.progressView.usesThreadedAnimation = false self.titleStackView.addArrangedSubview(infoLabel) self.infoLabel.font = .systemFont(ofSize: R.Size.controlFontSize) } } extension NSView { public func convertToScreen(_ rect: CGRect) -> CGRect { let windowRect = self.convert(rect, to: nil) guard let window = self.window else { return windowRect } return window.convertToScreen(windowRect) } } ================================================ FILE: DevToys/DevToys/Body/SettingView+.swift ================================================ // // SettingView+.swift // DevToys // // Created by yuki on 2022/02/16. // import CoreUtil final class SettingViewController: NSViewController { private let cell = SettingView() override func loadView() { self.view = cell } override func chainObjectDidLoad() { self.appModel.settings.$appearanceType .sink{[unowned self] in self.cell.appearancePicker.selectedItem = $0 }.store(in: &objectBag) self.cell.appearancePicker.itemPublisher .sink{[unowned self] in self.appModel.settings.appearanceType = $0 }.store(in: &objectBag) } } extension Settings.AppearanceType: TextItem { static let allCases: [Self] = [.useSystemSettings, .lightMode, .darkMode] var title: String { rawValue.localized() } } final private class SettingView: Page { let appearancePicker = EnumPopupButton() override func onAwake() { self.addSection( Area(icon: R.Image.paramators, title: "App Theme".localized(), message: "Select which app theme to display".localized(), control: appearancePicker) ) } } ================================================ FILE: DevToys/DevToys/Body/Text/HyphenationRemoverView+.swift ================================================ import CoreUtil final class HyphenationRemoverViewController: NSViewController { @RestorableState("hyphenation.rawCode") private var rawCode: String = "" @RestorableState("hyphenation.formattedCode") private var formattedCode: String = "" private let cell = HyphenationFormatterView() override func loadView() { self.view = cell } override func viewDidLoad() { self.$rawCode .sink{[unowned self] in cell.inputSection.string = $0 }.store(in: &objectBag) self.$formattedCode .sink{[unowned self] in cell.outputSection.string = $0 }.store(in: &objectBag) self.cell.inputSection.stringPublisher .sink{[unowned self] in self.rawCode = $0; updateFormattedCode() }.store(in: &objectBag) self.updateFormattedCode() } private func updateFormattedCode() { self.formattedCode = getFormattedText(rawCode) } private func getFormattedText(_ input: String) -> String { let lines = input .components(separatedBy: .newlines) .map { $0.replacingOccurrences(of: "- ", with: "") } let output = lines .joined(separator: " ") .replacingOccurrences(of: " ", with: "\n") return output } } final private class HyphenationFormatterView: Page { let inputSection = CodeViewSection(title: "Input".localized(), options: .defaultInput, language: .plaintext) let outputSection = CodeViewSection(title: "Output".localized(), options: .defaultOutput, language: .plaintext) private lazy var ioStack = self.addSection2(inputSection, outputSection) override func layout() { super.layout() self.ioStack.snp.remakeConstraints{ make in make.height.equalTo(max(240, self.frame.height - 150)) } } } ================================================ FILE: DevToys/DevToys/Body/Text/JSON Search/JSONNormalSearchView.swift ================================================ // // JSONNormalSearchView.swift // DevToys // // Created by yuki on 2022/02/25. // import CoreUtil final class JSONNormalSearchView: NSLoadView { private let textView = CodeTextView(language: .json) override func onAwake() { } } ================================================ FILE: DevToys/DevToys/Body/Text/JSON Search/JSONSearchView+.swift ================================================ // // JSONSearchView.swift // DevToys // // Created by yuki on 2022/02/25. // import CoreUtil final class JSONSearchViewController: NSViewController { private let cell = JSONSearchView() override func loadView() { self.view = cell } override func viewDidLoad() { } } enum JSONSearchType: String, TextItem { case nomral = "Normal Search" case regex = "Regex Search" case jsonPath = "JSONPath Search" var title: String { rawValue } } final private class JSONSearchView: Page { let searchTypePicker = EnumPopupButton() override func onAwake() { self.addSection(Section(title: "Configuration".localized(), items: [ Area(icon: R.Image.search, title: "Search Type", control: searchTypePicker) ])) } } ================================================ FILE: DevToys/DevToys/Body/Text/RegexTesterView+.swift ================================================ // // RegexTesterView+.swift // DevToys // // Created by yuki on 2022/02/03. // import CoreUtil final class RegexTesterViewController: NSViewController { private let cell = RegexTesterView() @RestorableState("rx.pattern") var pattern = #"(macOS|OS X) \d+\.\d+"# @RestorableState("rx.sample") var text = defaultText @Observable var regex: NSRegularExpression? = nil @Observable var isError = false @Observable var matches = [NSRange]() override func loadView() { self.view = cell } override func viewDidLoad() { self.cell.regexField.changeStringPublisher .sink{[unowned self] in self.pattern = $0; self.updateRegex() }.store(in: &objectBag) self.cell.textView.stringPublisher .sink{[unowned self] in self.text = $0; self.executeRegex() }.store(in: &objectBag) self.$isError.sink{[unowned self] in self.cell.regexField.isError = $0 }.store(in: &objectBag) self.$pattern.sink{[unowned self] in self.cell.regexField.string = $0 }.store(in: &objectBag) self.$text.sink{[unowned self] in self.cell.textView.string = $0 }.store(in: &objectBag) self.$matches.sink{[unowned self] in self.cell.textView.highlightRanges = $0 }.store(in: &objectBag) self.updateRegex() } private func updateRegex() { do { self.regex = try NSRegularExpression(pattern: pattern, options: .none) self.isError = false } catch { self.isError = true self.matches = [] self.regex = nil } self.executeRegex() } private func executeRegex() { guard let regex = self.regex else { return } let nsstring = text as NSString let matches = regex.matches(in: text, options: .none, range: NSRange(location: 0, length: nsstring.length)) var ranges = [NSRange]() for matche in matches { ranges.append(matche.range) } self.matches = ranges } } final private class RegexTesterView: Page { let regexField = TextField(showCopyButton: false) let textView = RegexTextView() override func layout() { super.layout() self.textView.snp.remakeConstraints{ make in make.height.equalTo(max(240, self.frame.height - 190)) } } override func onAwake() { self.regexField.isError = true self.regexField.font = .monospacedSystemFont(ofSize: R.Size.controlTitleFontSize, weight: .regular) self.addSection(Section(title: "Reguler expression".localized(), items: [regexField])) self.addSection(Section(title: "Text".localized(), items: [textView])) } } private let defaultText = """ OS X 10.9 Mavericks OS X 10.10 Yosemite OS X 10.11 El Capitan macOS 10.12 Sierra macOS 10.13 High Sierra macOS 10.14 Mojave macOS 10.15 Catalina macOS 11.0 Big Sur macOS 12.0 Monterey """ ================================================ FILE: DevToys/DevToys/Body/Text/TextDiffView+.swift ================================================ // // TextDiffView+.swift // DevToys // // Created by yuki on 2022/02/23. // import DiffMatchPatch import CoreUtil final class TextDiffViewController: NSViewController { @RestorableState("txtdiff.operation") var operation: TextCheckOperation = .characters @RestorableState("txtdiff.input1") var input1 = "Hello World!" @RestorableState("txtdiff.input2") var input2 = "Hello DevToys!" @Observable var diffAttributedString = NSAttributedString() private let cell = TextDiffView() override func loadView() { self.view = cell } override func viewDidLoad() { self.$operation .sink{[unowned self] in self.cell.checkOperationPicker.selectedItem = $0 }.store(in: &objectBag) self.$input1 .sink{[unowned self] in self.cell.input1Section.string = $0 }.store(in: &objectBag) self.$input2 .sink{[unowned self] in self.cell.input2Section.string = $0 }.store(in: &objectBag) self.$diffAttributedString .sink{[unowned self] in self.cell.outputSection.textView.textView.textStorage?.setAttributedString($0) }.store(in: &objectBag) self.cell.input1Section.stringPublisher .sink{[unowned self] in self.input1 = $0; updateDiff() }.store(in: &objectBag) self.cell.input2Section.stringPublisher .sink{[unowned self] in self.input2 = $0; updateDiff() }.store(in: &objectBag) self.cell.checkOperationPicker.itemPublisher .sink{[unowned self] in self.operation = $0; updateDiff() }.store(in: &objectBag) self.updateDiff() } private func updateDiff() { let diffs = TextDifferenceChecker.compare(input1, input2, operation: self.operation) self.diffAttributedString = buildAttributedString(from: diffs) } private func buildAttributedString(from diffs: [Difference]) -> NSAttributedString { let attributedString = NSMutableAttributedString() let defaultAttributes = [ NSAttributedString.Key.foregroundColor: NSColor.textColor, NSAttributedString.Key.font : NSFont.monospacedSystemFont(ofSize: 12, weight: .regular) ] for diff in diffs { switch diff.operation { case .equal: attributedString.append(NSAttributedString(string: diff.text, attributes: defaultAttributes)) case .insert: attributedString.append(NSAttributedString(string: diff.text, attributes: defaultAttributes.merging([ NSAttributedString.Key.backgroundColor : NSColor.systemGreen.withAlphaComponent(0.7) ], uniquingKeysWith: { a, _ in a }) )) case .delete: attributedString.append(NSAttributedString(string: diff.text, attributes: defaultAttributes.merging([ NSAttributedString.Key.backgroundColor : NSColor.systemRed.withAlphaComponent(0.7) ], uniquingKeysWith: { a, _ in a }) )) } } return attributedString } } extension TextCheckOperation: TextItem { public static let allCases: [TextCheckOperation] = [.characters, .words, .lines] var title: String { switch self { case .characters: return "By Characters".localized() case .words: return "By Words".localized() case .lines: return "By Lines".localized() } } } final private class TextDiffView: Page { let checkOperationPicker = EnumPopupButton() let input1Section = CodeViewSection(title: "Input 1".localized(), options: .defaultInput, language: .plaintext) let input2Section = CodeViewSection(title: "Input 2".localized(), options: .defaultInput, language: .plaintext) let outputSection = TextViewSection(title: "Output".localized(), options: .defaultOutput) override func layout() { super.layout() } override func onAwake() { self.addSection(Section(title: "Configuration".localized(), items: [ Area(icon: R.Image.format, title: "Diff Style".localized(), control: checkOperationPicker) ])) self.addSection2(input1Section, input2Section) self.input1Section.snp.makeConstraints{ make in make.height.equalTo(320) } self.addSection(outputSection) self.outputSection.textView.textView.font = .monospacedSystemFont(ofSize: 12, weight: .regular) self.outputSection.snp.makeConstraints{ make in make.height.equalTo(320) } } } ================================================ FILE: DevToys/DevToys/Body/Text/TextInspectorView+.swift ================================================ // // TextInspector.swift // DevToys // // Created by yuki on 2022/02/01. // import CoreUtil final class TextInspectorViewController: NSViewController { private let cell = TextInspectorView() @RestorableState("textin.input") private var input = "Hello World" @RestorableState("textin.output") private var output = "hello_world" @RestorableState("textin.convert") private var convertType: ConvertType = .camelCase @RestorableState("textin.info") private var information: String = "" override func loadView() { self.view = cell } private func updateInfo() { var result = "" func append(_ label: String, data: Any) { result += "\(label): ".padding(toLength: 24, withPad: " ", startingAt: 0) result += "\(data)" result += "\n" } append("Charactors".localized(), data: input.count) append("Words".localized(), data: input.split(separator: " ").count) append("Lines".localized(), data: input.split(separator: "\n").count) append("Bytes".localized(), data: input.data(using: .utf8)!.count) self.information = result } override func viewDidLoad() { self.$input .sink{[unowned self] in cell.inputSection.string = $0 }.store(in: &objectBag) self.$output .sink{[unowned self] in cell.outputSection.string = $0 }.store(in: &objectBag) self.$convertType.map{ ConvertType.allCases.firstIndex(of: $0) } .sink{[unowned self] in cell.tagCloudView.selectedItem = $0 }.store(in: &objectBag) self.$information .sink{[unowned self] in cell.informationView.string = $0 }.store(in: &objectBag) self.cell.inputSection.stringPublisher .sink{[unowned self] in self.input = $0; generate(); updateInfo() }.store(in: &objectBag) self.cell.tagCloudView.selectPublisher.map{ ConvertType.allCases[$0] } .sink{[unowned self] in self.convertType = $0; generate() }.store(in: &objectBag) self.updateInfo() } private func generate() { switch self.convertType { case .originalCase: self.output = input case .sentenceCase: self.output = input.capitalizingFirstLetter() case .lowerCase: self.output = input.lowercased() case .upperCase: self.output = input.uppercased() case .titleCase: self.output = input.capitalized case .camelCase: self.output = input.capitalized.split(separator: " ").joined().lowercaseFirstLetter() case .pascalCase: self.output = input.capitalized.split(separator: " ").joined() case .snakeCase: self.output = input.lowercased().split(separator: " ").joined(separator: "_") case .constantCase: self.output = input.uppercased().split(separator: " ").joined(separator: "_") case .kebabCase: self.output = input.lowercased().split(separator: " ").joined(separator: "-") case .cobolCase: self.output = input.uppercased().split(separator: " ").joined(separator: "-") case .trainCase: self.output = input.capitalized.split(separator: " ").joined(separator: "-") case .urlLowerSlugify: self.output = input.slugify(lowercase: true) case .urlPascalSlugify: self.output = input.slugify(lowercase: false) } } } extension String { func capitalizingFirstLetter() -> String { prefix(1).capitalized + dropFirst() } func lowercaseFirstLetter() -> String { prefix(1).lowercased() + dropFirst() } } private enum ConvertType: String, CaseIterable { case originalCase = "OriginalCase" case sentenceCase = "Sentence case" case lowerCase = "lower case" case upperCase = "UPPER CASE" case titleCase = "Title Case" case camelCase = "camelCase" case pascalCase = "PascalCase" case snakeCase = "snake_case" case constantCase = "CONSTANT_CASE" case kebabCase = "kebab-case" case cobolCase = "COBOL-CASE" case trainCase = "Traint-Case" case urlPascalSlugify = "URL-slugify" case urlLowerSlugify = "url-lower-slugify" } final class TextInspectorView: Page { let inputSection = TextViewSection(title: "Input".localized(), options: .defaultInput) let tagCloudView = TagCloudView() let outputSection = TextViewSection(title: "Output".localized(), options: .defaultOutput) let informationView = NSTextView() override func layout() { super.layout() self.outputSection.snp.remakeConstraints{ make in make.height.equalTo(max(240, self.frame.height - 360)) } } override func onAwake() { let convertSection = Section(title: "Convert".localized(), items: [tagCloudView]) self.addSection(convertSection) self.tagCloudView.items = ConvertType.allCases.map{ $0.rawValue } self.tagCloudView.isSelectable = true self.addSection2(inputSection, outputSection) self.tagCloudView.snp.remakeConstraints{ make in make.height.equalTo(68) make.width.equalToSuperview() } self.informationView.font = .monospacedSystemFont(ofSize: R.Size.controlTitleFontSize, weight: .regular) self.informationView.snp.makeConstraints{ make in make.height.equalTo(100) } self.informationView.backgroundColor = .clear self.informationView.isEditable = false self.addSection(Section(title: "Information".localized(), items: [ informationView ])) } } ================================================ FILE: DevToys/DevToys/Body/Utils/CameraViewController.swift ================================================ // // ViewController.swift // macOS Camera // // Created by Mihail Șalari. on 4/24/17. // Copyright © 2017 Mihail Șalari. All rights reserved. // import CoreUtil import AVFoundation final class CameraManager { let videoSession = AVCaptureSession() private var cameraDevice: AVCaptureDevice! private let sessionQueue = DispatchQueue(label: "buffer.queue") private var isPrepared = false func startSession(_ delegate: AVCaptureVideoDataOutputSampleBufferDelegate) { if !isPrepared { self.prepareCamera(delegate) isPrepared = true } if !videoSession.isRunning { videoSession.startRunning() } } func stopSession() { if videoSession.isRunning { videoSession.stopRunning() } } private func findCameraDevice() -> AVCaptureDevice? { let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .unspecified) guard let device = discoverySession.devices.first, device.hasMediaType(AVMediaType.video) else { return nil } return device } private func prepareCamera(_ delegate: AVCaptureVideoDataOutputSampleBufferDelegate) { guard let device = self.findCameraDevice() else { return } self.cameraDevice = device self.videoSession.sessionPreset = .photo do { let input = try AVCaptureDeviceInput(device: device) if videoSession.canAddInput(input) { videoSession.addInput(input) } } catch { print(error.localizedDescription) } let videoOutput = AVCaptureVideoDataOutput() videoOutput.setSampleBufferDelegate(delegate, queue: sessionQueue) if videoSession.canAddOutput(videoOutput) { videoSession.addOutput(videoOutput) } } } extension AVAuthorizationStatus: CustomStringConvertible { public var description: String { switch self { case .notDetermined: return "notDetermined" case .restricted: return "restricted" case .denied: return "denied" case .authorized: return "authorized" @unknown default: fatalError() } } } ================================================ FILE: DevToys/DevToys/Body/Utils/FileConflictAvoider.swift ================================================ // // FileConflictAvoider.swift // DevToys // // Created by yuki on 2022/02/22. // import CoreUtil enum FileConflictAvoider { private static let autoFilenameRegex = try! NSRegularExpression(pattern: #"(.*)\s\(\d+\)$"#, options: []) static func avoidConflict(_ url: URL) -> URL { let ext = url.pathExtension let basename = url.deletingLastPathComponent() let filename = filterAutoSuffix(url.deletingPathExtension().lastPathComponent) let rfilename = Identifier.make(with: .brackedNumberPostfix(filename)) { !FileManager.default.fileExists(atPath: basename.appendingPathComponent($0).appendingPathExtension(ext).path) } let result = basename.appendingPathComponent(rfilename).appendingPathExtension(ext) return result } private static func filterAutoSuffix(_ filename: String) -> String { let nsString = filename as NSString guard let match = autoFilenameRegex.matches(in: filename, options: [], range: NSRange(location: 0, length: nsString.length)).first else { return filename } print(match) guard match.numberOfRanges >= 2 else { return filename } let basename = nsString.substring(with: match.range(at: 1)) return basename } } ================================================ FILE: DevToys/DevToys/Body/Utils/Identifier.swift ================================================ // // Identifier.swift // CoreUtil // // Created by yuki on 2021/04/29. // Copyright © 2021 yuki. All rights reserved. // public enum Identifier { @inlinable public static func make(with rule: Identifier.Rule, notContainsIn set: Set) -> String { make(with: rule) { !set.contains($0) } } @inlinable public static func make(with rule: Identifier.Rule, _ condition: (String) -> Bool) -> String { var identifier = rule.next() while !condition(identifier) { identifier = rule.next() } return identifier } public struct Rule { @usableFromInline let next: () -> String @inlinable public init(_ next: @escaping ()->String) { self.next = next } } } extension Identifier.Rule { @inlinable public static func uuid() -> Identifier.Rule { Identifier.Rule { UUID().uuidString } } @inlinable public static func brackedNumberPostfix(_ base: String, separtor: String = " ") -> Identifier.Rule { var counter = 0 return Identifier.Rule { defer { counter += 1 } return counter == 0 ? base : base + "\(separtor)(\(counter))" } } @inlinable public static func numberPostfix(_ base: String, separtor: String = " ") -> Identifier.Rule { var counter = 0 return Identifier.Rule { defer { counter += 1 } return counter == 0 ? base : "\(base)\(separtor)\(counter)" } } } ================================================ FILE: DevToys/DevToys/Body/Utils/ImageDropper.swift ================================================ // // ImageDropper.swift // DevToys // // Created by yuki on 2022/02/04. // import Cocoa enum ImageDropper { static func images(fromPasteboard pasteboard: NSPasteboard) -> [ImageItem] { var newImageItems = [ImageItem]() guard let items = pasteboard.pasteboardItems else { return [] } if items.count == 1, let url = (pasteboard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL])?.first { guard let image = NSImage(pasteboard: pasteboard) else { return [] } newImageItems = [ImageItem(fileURL: url, image: image)] } else { guard let urls = pasteboard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL] else { return [] } let images = urls.compactMap{ url in NSImage(contentsOf: url).map{ ImageItem(fileURL: url, image: $0) } } newImageItems = images } return newImageItems } } ================================================ FILE: DevToys/DevToys/Body/Utils/Slugify.swift ================================================ // // Slugify.swift // DevToys // // Created by yuki on 2022/02/26. // import Foundation extension String { public func slugify(delimiter: String = "-", lowercase: Bool) -> String { var slug = self if lowercase { slug = slug.lowercased() } slugifyReplacements.forEach{ slug = slug.replacingOccurrences(of: $0, with: $1) } slug = dupeSpaceRegExp.stringByReplacingMatches(in: slug, options: [], range: NSRange(location: 0, length: (slug as NSString).length), withTemplate: " ") slug = punctuationRegExp.stringByReplacingMatches(in: slug, options: [], range: NSRange(location: 0, length: (slug as NSString).length), withTemplate: "") slug = slug.replacingOccurrences(of: " ", with: delimiter) return slug } } private let dupeSpaceRegExp = try! NSRegularExpression(pattern: #"\s{2,}"#, options: []) private let punctuationRegExp = try! NSRegularExpression(pattern: #"[^\w\s-]"#, options: []) private let slugifyReplacements = [ "¹": "1", "²": "2", "³": "3", "º": "o", "°": "0", "æ": "ae", "ǽ": "ae", "À": "A", "Á": "A", "Â": "A", "Ã": "A", "Å": "A", "Ǻ": "A", "Ă": "A", "Ǎ": "A", "Ạ": "A", "Ả": "A", "ả": "a", "ạ": "a", "Ầ": "A", "ầ": "a", "Ẩ": "A", "ẩ": "a", "Ẫ": "A", "ẫ": "a", "Ậ": "A", "ậ": "a", "Ắ": "A", "ắ": "a", "Ằ": "Ằ", "ằ": "ằ", "Ẳ": "A", "ẳ": "a", "Ẵ": "A", "ẵ": "a", "Ặ": "A", "ặ": "a", "Æ": "AE", "Ǽ": "AE", "à": "a", "á": "a", "â": "a", "ã": "a", "å": "a", "ǻ": "a", "ă": "a", "ǎ": "a", "ª": "a", "@": "at", "&": "and", "Ĉ": "CX", "Ċ": "C", "ĉ": "cx", "ċ": "c", "©": "c", "Ð": "Dj", "Đ": "Dj", "ð": "dj", "đ": "dj", "È": "E", "É": "E", "Ê": "E", "Ë": "E", "Ĕ": "E", "Ė": "E", "è": "e", "é": "e", "ê": "e", "ë": "e", "ĕ": "e", "ė": "e", "Ệ": "E", "ệ": "e", "Ể": "E", "ể": "e", "Ẹ": "E", "ẻ": "e", "Ẻ": "E", "ẽ": "e", "Ẽ": "E", "ễ": "e", "Ễ": "E", "ẹ": "e", "ƒ": "f", "Ĝ": "GX", "Ġ": "G", "ĝ": "gx", "ġ": "g", "Ĥ": "HX", "Ħ": "H", "ĥ": "hx", "ħ": "h", "Ì": "I", "Í": "I", "Î": "I", "Ï": "I", "Ĩ": "I", "Ĭ": "I", "Ǐ": "I", "Į": "I", "IJ": "IJ", "ì": "i", "í": "i", "î": "i", "ï": "i", "ĩ": "i", "ĭ": "i", "ǐ": "i", "į": "i", "Ỉ": "I", "ỉ": "i", "Ị": "I", "ị": "i", "ij": "ij", "Ĵ": "JX", "ĵ": "jx", "Ĺ": "L", "Ľ": "L", "Ŀ": "L", "ĺ": "l", "ľ": "l", "ŀ": "l", "Ñ": "N", "ñ": "n", "ʼn": "n", "Ò": "O", "Ô": "O", "Õ": "O", "Ō": "O", "Ŏ": "O", "Ǒ": "O", "Ő": "O", "Ơ": "O", "Ø": "O", "Ǿ": "O", "Œ": "OE", "ò": "o", "ô": "o", "õ": "o", "ō": "o", "ŏ": "o", "ǒ": "o", "ő": "o", "ơ": "o", "ø": "o", "ǿ": "o", "Ọ": "O", "ọ": "o", "Ổ": "O", "ỗ": "o", "Ỗ": "o", "ổ": "o", "Ộ": "O", "ộ": "o", "ợ": "o", "Ợ": "o", "Ở": "O", "ở": "o", "Ỡ": "O", "ỡ": "o", "œ": "oe", "Ŕ": "R", "Ŗ": "R", "ŕ": "r", "ŗ": "r", "Ŝ": "SX", "Ș": "S", "ŝ": "sx", "ș": "s", "ſ": "s", "Ţ": "T", "Ț": "T", "Ŧ": "T", "Þ": "TH", "ţ": "t", "ț": "t", "ŧ": "t", "þ": "th", "Ù": "U", "Ú": "U", "Û": "U", "Ũ": "U", "Ŭ": "UX", "Ű": "U", "Ų": "U", "Ư": "U", "Ǔ": "U", "Ǖ": "U", "Ǘ": "U", "Ǚ": "U", "Ǜ": "U", "ù": "u", "ú": "u", "û": "u", "ũ": "u", "ŭ": "ux", "ű": "u", "ų": "u", "ư": "u", "ǔ": "u", "ǖ": "u", "ǘ": "u", "ǚ": "u", "ǜ": "u", "Ụ": "U", "Ủ": "U", "ủ": "u", "ụ": "u", "Ŵ": "W", "ŵ": "w", "Ý": "Y", "Ÿ": "Y", "Ŷ": "Y", "ý": "y", "ÿ": "y", "ŷ": "y", "Ъ": "", "Ь": "", "А": "A", "Б": "B", "Ц": "C", "Ч": "Ch", "Д": "D", "Е": "E", "Ё": "E", "Э": "E", "Ф": "F", "Г": "G", "Х": "H", "И": "I", "Й": "J", "Я": "Ja", "Ю": "Ju", "К": "K", "Л": "L", "М": "M", "Н": "N", "О": "O", "П": "P", "Р": "R", "С": "S", "Ш": "Sh", "Щ": "Shch", "Т": "T", "У": "U", "В": "V", "Ы": "Y", "З": "Z", "Ж": "Zh", "ъ": "", "ь": "", "а": "a", "б": "b", "ц": "c", "ч": "ch", "д": "d", "е": "e", "ё": "e", "э": "e", "ф": "f", "г": "g", "х": "h", "и": "i", "й": "j", "я": "ja", "ю": "ju", "к": "k", "л": "l", "м": "m", "н": "n", "о": "o", "п": "p", "р": "r", "с": "s", "ш": "sh", "щ": "shch", "т": "t", "у": "u", "в": "v", "ы": "y", "з": "z", "ж": "zh", "Ä": "AE", "Ö": "OE", "Ü": "UE", "ß": "ss", "ä": "ae", "ö": "oe", "ü": "ue", "Ç": "C", "Ğ": "G", "İ": "I", "Ş": "S", "ç": "c", "ğ": "g", "ı": "i", "ş": "s", "Ā": "A", "Ē": "E", "Ģ": "G", "Ī": "I", "Ķ": "K", "Ļ": "L", "Ņ": "N", "Ū": "U", "ā": "a", "ē": "e", "ģ": "g", "ī": "i", "ķ": "k", "ļ": "l", "ņ": "n", "ū": "u", "Ґ": "G", "І": "I", "Ї": "Ji", "Є": "Ye", "ґ": "g", "і": "i", "ї": "ji", "є": "ye", "Č": "C", "Ď": "Dj", "Ě": "E", "Ň": "N", "Ř": "R", "Š": "S", "Ť": "T", "Ů": "U", "Ž": "Z", "č": "c", "ď": "dj", "ě": "e", "ň": "n", "ř": "r", "š": "s", "ť": "t", "ů": "u", "ž": "z", "Ą": "A", "Ć": "C", "Ę": "E", "Ł": "L", "Ń": "N", "Ó": "O", "Ś": "S", "Ź": "Z", "Ż": "Z", "ą": "a", "ć": "c", "ę": "e", "ł": "l", "ń": "n", "ó": "o", "ś": "s", "ź": "z", "ż": "z", "Α": "A", "Β": "B", "Γ": "G", "Δ": "D", "Ε": "E", "Ζ": "Z", "Η": "E", "Θ": "Th", "Ι": "I", "Κ": "K", "Λ": "L", "Μ": "M", "Ν": "N", "Ξ": "X", "Ο": "O", "Π": "P", "Ρ": "R", "Σ": "S", "Τ": "T", "Υ": "Y", "Ỷ": "Y", "ỷ": "y", "Ỹ": "Y", "ỹ": "y", "Ỵ": "Y", "ỵ": "y", "Φ": "Ph", "Χ": "Ch", "Ψ": "Ps", "Ω": "O", "Ϊ": "I", "Ϋ": "Y", "ά": "a", "έ": "e", "ή": "e", "ί": "i", "ΰ": "Y", "α": "a", "β": "b", "γ": "g", "δ": "d", "ε": "e", "ζ": "z", "η": "e", "θ": "th", "ι": "i", "κ": "k", "λ": "l", "μ": "m", "ν": "n", "ξ": "x", "ο": "o", "π": "p", "ρ": "r", "ς": "s", "σ": "s", "τ": "t", "υ": "y", "φ": "ph", "χ": "ch", "ψ": "ps", "ω": "o", "ϊ": "i", "ϋ": "y", "ό": "o", "ύ": "y", "ώ": "o", "ϐ": "b", "ϑ": "th", "ϒ": "Y", "أ": "a", "ب": "b", "ت": "t", "ث": "th", "ج": "g", "ح": "h", "خ": "kh", "د": "d", "ذ": "th", "ر": "r", "ز": "z", "س": "s", "ش": "sh", "ص": "s", "ض": "d", "ط": "t", "ظ": "th", "ع": "aa", "غ": "gh", "ف": "f", "ق": "k", "ك": "k", "ل": "l", "م": "m", "ن": "n", "ه": "h", "و": "o", "ي": "y" ] ================================================ FILE: DevToys/DevToys/Body/Utils/ffmpeg/FFMpegExecutor.swift ================================================ // // FFMpegExecutor.swift // DevToys // // Created by yuki on 2022/02/20. // import CoreUtil enum FFExecutor { private static let ffmpegURL = Bundle.current.url(forResource: "ffmpeg", withExtension: nil)! static func execute(_ arguments: [String], inputURL: URL, destinationURL: URL) -> Promise { let arguments = ["-i", inputURL.path, "-y"] + arguments + [destinationURL.path] let task = Process() let outputPipe = Pipe() let errorPipe = Pipe() task.executableURL = ffmpegURL task.arguments = arguments task.standardOutput = outputPipe task.standardError = errorPipe var duration: FFTime? { didSet { durationPromise.fullfill(duration) } } let durationPromise = Promise() let completePromise = Promise.tryAsync{ resolve, reject in try task.run() task.waitUntilExit() if task.terminationStatus != 0 { reject(TerminalError.nonZeroExit(errorPipe.readStringToEndOfFile ?? "[binary]")) } else { resolve(()) } } let dtask = durationPromise .map{ FFTask(duration: $0, complete: completePromise) } let ctask = completePromise .replaceError(with: ()) .map{ FFTask(duration: nil, complete: completePromise) } let taskPromise = Promise.first([dtask, ctask])! DispatchQueue.global().async { while let data = errorPipe.fileHandleForReading.availableData.nonEmptyOrNil() { guard let nsString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { continue } if duration == nil, let fduration = findDuration(in: nsString) { duration = fduration } else if case .fulfilled(let task) = taskPromise.state, let report = FFProgressReport(progressString: nsString as String) { task.sendReport(report) } } } return taskPromise } private static func findDuration(in nsString: NSString) -> FFTime? { enum __ { static let rx = try! NSRegularExpression(pattern: "Duration: (.*?),", options: []) } let matches = __.rx.matches(in: nsString as String, options: [], range: NSRange(location: 0, length: nsString.length)) guard let match = matches.first, match.numberOfRanges == 2 else { return nil } let timeString = nsString.substring(with: match.range(at: 1)) return FFTime(timeString: timeString) } } extension Data { func nonEmptyOrNil() -> Data? { if isEmpty { return nil } return self } } extension Promise { public static func first(_ promises: [Promise]) -> Promise? { if promises.isEmpty { return nil } return Promise{ resolve, reject in for promise in promises { promise.sink(resolve, reject) } } } } ================================================ FILE: DevToys/DevToys/Body/Utils/ffmpeg/FFProgressReport.swift ================================================ // // FFProgressReport.swift // DevToys // // Created by yuki on 2022/02/20. // import CoreUtil struct FFProgressReport { let speed: Double let time: FFTime let progress: Progress enum Progress { case `continue` case end } init?(progressData: Data) { if progressData.isEmpty { return nil } guard let string = String(data: progressData, encoding: .utf8) else { return nil } self.init(progressString: string) } init?(progressString: String) { enum Rx { static let time = try! NSRegularExpression(pattern: "time=(.*)", options: []) static let speed = try! NSRegularExpression(pattern: "speed=(.*)", options: []) } let nsString = progressString as NSString let range = NSRange(location: 0, length: nsString.length) guard let timeMatch = Rx.time.matches(in: progressString, options: [], range: range).first, let time = FFTime(timeString: nsString.substring(with: timeMatch.range(at: 1))) else { return nil } self.time = time guard let speedMatch = Rx.speed.matches(in: progressString, options: [], range: range).first, let speed = Scanner(string: nsString.substring(with: speedMatch.range(at: 1))).scanDouble() else { return nil } self.speed = speed let isEnd = nsString.contains("Lsize") if isEnd { self.progress = .end } else { self.progress = .continue } } } ================================================ FILE: DevToys/DevToys/Body/Utils/ffmpeg/FFTask.swift ================================================ // // FFTask.swift // DevToys // // Created by yuki on 2022/02/21. // import CoreUtil final class FFTask { let duration: FFTime? let complete: Promise @Observable var progress = 0.0 func sendReport(_ report: FFProgressReport) { guard let duration = duration else { return } let durationInterval = duration.timeInterval let reportInterval = report.time.timeInterval self.progress = reportInterval / durationInterval } init(duration: FFTime?, complete: Promise) { self.duration = duration self.complete = complete self.complete.finally { self.progress = 1 } } } ================================================ FILE: DevToys/DevToys/Body/Utils/ffmpeg/FFThumnailGenerator.swift ================================================ // // FFThumnailGenerator.swift // DevToys // // Created by yuki on 2022/02/21. // import CoreUtil enum FFThumnailGenerator { private static let thumbnmailURL = FileManager.temporaryDirectoryURL.appendingPathExtension("FFThumbnail") => { try? FileManager.default.createDirectory(at: $0, withIntermediateDirectories: true, attributes: nil) } static func thumbnail(of movieURL: URL, size: CGSize) -> Promise { let filename = UUID().uuidString + ".jpg" let thumbURL = self.thumbnmailURL.appendingPathComponent(filename) let task = FFExecutor.execute([ "-vframes", "1", "-f", "image2", "-vf", "scale=\(size.width):\(size.height):force_original_aspect_ratio=decrease" ], inputURL: movieURL, destinationURL: thumbURL) return task.eraseToError().flatMap{ $0.complete } .map{ NSImage(contentsOf: thumbURL) } .replaceError(with: nil) } } ================================================ FILE: DevToys/DevToys/Body/Utils/ffmpeg/FFTime.swift ================================================ // // FFTime.swift // DevToys // // Created by yuki on 2022/02/20. // import Foundation struct FFTime: CustomStringConvertible { let hour: Int let minute: Int let second: Int let ds: Int var timeInterval: TimeInterval { Double(hour)*60*60 + Double(minute)*60 + Double(second) + Double(ds)/100 } var description: String { "FFTime(\(hour):\(minute):\(second).\(ds))" } init?(timeString: String) { let scanner = Scanner(string: timeString) guard let hour = scanner.scanInt(), let s1 = scanner.scanCharacter(), s1 == ":", let minute = scanner.scanInt(), let s2 = scanner.scanCharacter(), s2 == ":", let second = scanner.scanInt(), let s3 = scanner.scanCharacter(), s3 == ".", let ds = scanner.scanInt() else { return nil } self.hour = hour self.minute = minute self.second = second self.ds = ds } } ================================================ FILE: DevToys/DevToys/Body/Utils/ffmpeg/ffmpeg ================================================ [File too large to display: 21.6 MB] ================================================ FILE: DevToys/DevToys/Bridging-Header.h ================================================ // // Bridging-Header.h // DevToys // // Created by yuki on 2022/02/19. // #ifndef Bridging_Header_h #define Bridging_Header_h #import "ShowAndHideCursor.h" #endif /* Bridging_Header_h */ ================================================ FILE: DevToys/DevToys/Component/Area.swift ================================================ // // ComponentBaseView.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil final class Area: NSLoadView { var control: NSView? { didSet { oldValue?.removeFromSuperview() if let control = control { self.stackView.addArrangedSubview(control) } } } var icon: NSImage? { get { iconView.image } set { iconView.image = newValue; iconView.isHidden = icon == nil } } var title: String { get { titleLabel.stringValue } set { titleLabel.stringValue = newValue } } var message: String? = "Message" { didSet { messageLabel.isHidden = message == nil messageLabel.stringValue = message ?? "" } } var insetLevel = 0 { didSet { self.stackView.edgeInsets.left = CGFloat(insetLevel + 1) * 16 } } convenience init(icon: NSImage? = nil, title: String, message: String? = nil, control: NSView) { self.init() self.setup(icon: icon, title: title, message: message, control: control) } private func setup(icon: NSImage?, title: String, message: String?, control: NSView) { self.icon = icon self.title = title self.message = message self.control = control } private let iconView = NSImageView() private let titleLabel = NSTextField(labelWithString: "Title") private let messageLabel = NSTextField(labelWithString: "Message") private let titleStack = NSStackView() private let stackView = NSStackView() private let backgroundLayer = ControlBackgroundLayer.animationDisabled() override func updateLayer() { self.backgroundLayer.update() } override func layout() { super.layout() var edgeInsets = NSEdgeInsets.zero edgeInsets.left = CGFloat(insetLevel) * 16 self.backgroundLayer.frame = bounds.slimmed(by: edgeInsets) } override func onAwake() { self.wantsLayer = true self.layer?.addSublayer(backgroundLayer) self.snp.makeConstraints{ make in make.height.equalTo(42) } self.addSubview(stackView) self.stackView.orientation = .horizontal self.stackView.distribution = .fill self.stackView.alignment = .centerY self.stackView.edgeInsets = .init(x: 16, y: 0) self.stackView.spacing = 16 self.stackView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.stackView.addArrangedSubview(iconView) self.iconView.image = R.Image.Tool.base64Coder self.iconView.snp.makeConstraints{ make in make.size.equalTo(24) } self.stackView.addArrangedSubview(titleStack) self.titleStack.orientation = .vertical self.titleStack.spacing = 4 self.titleStack.distribution = .fillProportionally self.titleStack.alignment = .left self.titleStack.addArrangedSubview(titleLabel) self.titleLabel.font = NSFont.systemFont(ofSize: R.Size.controlTitleFontSize) self.titleStack.addArrangedSubview(messageLabel) self.messageLabel.isHidden = true self.messageLabel.font = NSFont.systemFont(ofSize: R.Size.controlFontSize) self.messageLabel.textColor = .secondaryLabelColor self.messageLabel.lineBreakMode = .byTruncatingTail self.stackView.addArrangedSubview(NSView()) } } ================================================ FILE: DevToys/DevToys/Component/Button.swift ================================================ // // Button.swift // DevToys // // Created by yuki on 2022/02/01. // import CoreUtil final class Button: NSLoadButton { override var intrinsicContentSize: NSSize { super.intrinsicContentSize + [64, 0] } override func draw(_ dirtyRect: NSRect) { if isHighlighted { R.Color.controlAccentColor.shadow(withLevel: 0.1)?.setFill() } else { R.Color.controlAccentColor.setFill() } NSBezierPath(roundedRect: bounds, xRadius: R.Size.corner, yRadius: R.Size.corner).fill() (self.title as NSString).draw(center: bounds, attributes: [NSAttributedString.Key.foregroundColor : NSColor.white]) } override func onAwake() { self.snp.makeConstraints{ make in make.height.equalTo(R.Size.controlHeight) } self.isBordered = false self.bezelStyle = .rounded } } extension R.Color { static var controlAccentColor: NSColor { NSColor.controlAccentColor } } ================================================ FILE: DevToys/DevToys/Component/CodeTextView.swift ================================================ // // CodeTextView.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil import Highlightr import AppKit final class CodeTextView: NSLoadView { var string: String { get { textView.string } set { textView.setString(newValue) } } var isEditable: Bool { get { textView.isEditable } set { textView.isEditable = newValue } } override var isSelectable: Bool { get { textView.isSelectable } set { textView.isSelectable = newValue } } var language: Language = .json { didSet { textStorage.language = language.rawValue } } var contentInsets = NSSize.zero { didSet { textView?.textContainerInset = contentInsets } } var stringPublisher: AnyPublisher { textView.stringSubject.map{ self.textView.string }.eraseToAnyPublisher() } convenience init(language: Language) { self.init() setLanguage(language: language) } private func setLanguage(language: Language) { self.language = language } func becomeFocused() { self.window?.makeFirstResponder(self.textView) } private(set) var scrollView: NSScrollView! private(set) var textView: _CodeTextView! private let highlightr = Highlightr()! private lazy var textStorage = CodeAttributedString(highlightr: highlightr) => { $0.language = "Json" } override func viewDidChangeEffectiveAppearance() { if self.effectiveAppearance.name == .darkAqua { highlightr.setTheme(to: "vs2015") } else { highlightr.setTheme(to: "vs") } highlightr.theme.setCodeFont(.monospacedSystemFont(ofSize: R.Size.codeFontSize, weight: .regular)) } override func onAwake() { self.viewDidChangeEffectiveAppearance() self.wantsLayer = true self.layer?.cornerRadius = R.Size.corner let layoutManager = NSLayoutManager() textStorage.addLayoutManager(layoutManager) let textContainer = NSTextContainer(size: [0, 10000000]) textContainer.widthTracksTextView = true textContainer.heightTracksTextView = false textContainer.lineBreakMode = .byWordWrapping layoutManager.addTextContainer(textContainer) _CodeTextView.textContainer = textContainer let scrollView = _CodeTextView.scrollableTextView() self.scrollView = scrollView _CodeTextView.textContainer = nil self.addSubview(scrollView) scrollView.automaticallyAdjustsContentInsets = false scrollView.snp.makeConstraints{ make in make.edges.equalToSuperview() } let textView = scrollView.documentView as! _CodeTextView self.textView = textView self.textView.allowsUndo = true self.textView.usesFindBar = true self.textView.isAutomaticSpellingCorrectionEnabled = false self.textView.isAutomaticQuoteSubstitutionEnabled = false self.textView.isAutomaticDashSubstitutionEnabled = false self.textView.isAutomaticLinkDetectionEnabled = false self.contentInsets = [0, 4] self.textView?.backgroundColor = .quaternaryLabelColor } } final class _CodeTextView: NSTextView { static var textContainer: NSTextContainer? let stringSubject = PassthroughSubject() var sendingValue = false func setString(_ string: String) { if !sendingValue { self.string = string } } override init(frame frameRect: NSRect) { super.init(frame: frameRect) self.commonInit() } override init(frame frameRect: NSRect, textContainer container: NSTextContainer?) { super.init(frame: frameRect, textContainer: _CodeTextView.textContainer) self.commonInit() } private func commonInit() {} override func didChangeText() { sendingValue = true stringSubject.send() sendingValue = false } override func insertText(_ value: Any, replacementRange: NSRange) { guard let string = value as? String, let selectedRange = self.selectedRanges.first?.rangeValue else { return super.insertText(value, replacementRange: replacementRange) } let singlePairs = ["(": (start: "(", end: ")"), "[": (start: "[", end: "]"), "{": (start: "{", end: "}")] let doublePaits = ["\"": (start: "\"", end: "\""), "'": (start: "'", end: "'")] func pairWords(_ pair: (start: String, end: String)) { let nsstring = self.string as NSString var selectedWord: String? objc_try({ selectedWord = nsstring.substring(with: selectedRange) }, catch: {_ in}) guard let _selectedWord = selectedWord else { return } let word = pair.start + _selectedWord + pair.end super.insertText(word, replacementRange: replacementRange) super.setSelectedRange(NSRange(location: selectedRange.location + pair.start.count, length: selectedRange.length)) } if let pair = singlePairs[string] { return pairWords(pair) } if let pair = doublePaits[string], selectedRange.length > 0 { return pairWords(pair) } if let selectedRange = self.selectedRanges.first?.rangeValue { let nsstring = self.string as NSString var _cursorChar: String? objc_try({ _cursorChar = nsstring.substring(with: NSRange(location: selectedRange.location - 1, length: 1)) }, catch: {_ in}) guard let cursorChar = _cursorChar else { return super.insertText(string, replacementRange: replacementRange) } if cursorChar == "(", string == ")" { return moveRight(nil) } if cursorChar == "{", string == "}" { return moveRight(nil) } if cursorChar == "[", string == "]" { return moveRight(nil) } } super.insertText(string, replacementRange: replacementRange) } override func insertNewline(_ sender: Any?) { guard let selectedRange = self.selectedRanges.first?.rangeValue else { return super.insertNewline(sender) } let nsstring = self.string as NSString let cursorChar = nsstring.substring(with: NSRange(location: selectedRange.location - 1, length: 1)) var cursorNextChar: String? objc_try { cursorNextChar = nsstring.substring(with: NSRange(location: selectedRange.location, length: 1)) } catch: {_ in} let (line, _) = self.getLine(nsstring, in: selectedRange) let indent = self.getIndentOfLine(line, spaceCount: 4) if (cursorChar == "{" && cursorNextChar == "}") || (cursorChar == "[" && cursorNextChar == "]") { self.insertText("\n" + String(repeating: " ", count: indent+1) + "\n" + String(repeating: " ", count: indent), replacementRange: selectedRange) self.moveUp(nil) self.moveToEndOfLine(nil) } else { self.insertText("\n" + String(repeating: " ", count: indent), replacementRange: selectedRange) } } override func moveToBeginningOfLineAndModifySelection(_ sender: Any?) { guard let selectedRange = self.selectedRanges.first?.rangeValue else { return super.moveToBeginningOfLineAndModifySelection(sender) } let nsstring = self.string as NSString let (line, lineRange) = self.getLine(nsstring, in: selectedRange) let spaceCount = self.beginningLineWhiteSpaceCount(line) let spaceStartLocation = lineRange.location + spaceCount let isSpaces = selectedRange.location <= spaceStartLocation if isSpaces { super.moveToBeginningOfLineAndModifySelection(sender) } else { let lineCount = line.count(where: { $0 != "\n" }) let range = NSRange(location: selectedRange.location + spaceCount - lineCount, length: lineCount - spaceCount) self.setSelectedRange(range) } } private func beginningLineWhiteSpaceCount(_ line: String) -> Int { var count = 0 for c in line { if c == "\t" || c == " " { count += 1 } else { return count } } return count } private func getIndentOfLine(_ line: String, spaceCount: Int) -> Int { var indent = 0 var space = 0 for c in line { if c == "\t" { indent += 1 } else if c == " " { space += 1 } else { return indent } if space == spaceCount { indent += 1 space = 0 } } return indent } private func getLine(_ nsString: NSString, in range: NSRange) -> (String, NSRange) { var start = -1 var end = -1 nsString.getLineStart(&start, end: &end, contentsEnd: nil, for: range) let range = NSRange(location: start, length: end - start) var line: String? objc_try({ line = nsString.substring(with: range) }, catch: {_ in }) return (line ?? "", range) } required init?(coder: NSCoder) { fatalError() } } extension CodeTextView { enum Language: String { case abnf = "abnf" case accesslog = "accesslog" case actionscript = "actionscript" case ada = "ada" case angelscript = "angelscript" case apache = "apache" case applescript = "applescript" case arcade = "arcade" case cpp = "cpp" case arduino = "arduino" case armasm = "armasm" case xml = "xml" case asciidoc = "asciidoc" case aspectj = "aspectj" case autohotkey = "autohotkey" case autoit = "autoit" case avrasm = "avrasm" case awk = "awk" case axapta = "axapta" case bash = "bash" case basic = "basic" case bnf = "bnf" case brainfuck = "brainfuck" case cal = "cal" case capnproto = "capnproto" case ceylon = "ceylon" case clean = "clean" case clojure = "clojure" case clojureRepl = "clojure-repl" case cmake = "cmake" case coffeescript = "coffeescript" case coq = "coq" case cos = "cos" case crmsh = "crmsh" case crystal = "crystal" case cs = "cs" case csp = "csp" case css = "css" case d = "d" case markdown = "markdown" case dart = "dart" case delphi = "delphi" case diff = "diff" case django = "django" case dns = "dns" case dockerfile = "dockerfile" case dos = "dos" case dsconfig = "dsconfig" case dts = "dts" case dust = "dust" case ebnf = "ebnf" case elixir = "elixir" case elm = "elm" case ruby = "ruby" case erb = "erb" case erlangRepl = "erlang-repl" case erlang = "erlang" case excel = "excel" case fix = "fix" case flix = "flix" case fortran = "fortran" case fsharp = "fsharp" case gams = "gams" case gauss = "gauss" case gcode = "gcode" case gherkin = "gherkin" case glsl = "glsl" case gml = "gml" case go = "go" case golo = "golo" case gradle = "gradle" case groovy = "groovy" case haml = "haml" case handlebars = "handlebars" case haskell = "haskell" case haxe = "haxe" case hsp = "hsp" case htmlbars = "htmlbars" case http = "http" case hy = "hy" case inform7 = "inform7" case ini = "ini" case irpf90 = "irpf90" case isbl = "isbl" case java = "java" case javascript = "javascript" case jbossCli = "jboss-cli" case json = "json" case julia = "julia" case juliaRepl = "julia-repl" case kotlin = "kotlin" case lasso = "lasso" case ldif = "ldif" case leaf = "leaf" case less = "less" case lisp = "lisp" case livecodeserver = "livecodeserver" case livescript = "livescript" case llvm = "llvm" case lsl = "lsl" case lua = "lua" case makefile = "makefile" case mathematica = "mathematica" case matlab = "matlab" case maxima = "maxima" case mel = "mel" case mercury = "mercury" case mipsasm = "mipsasm" case mizar = "mizar" case perl = "perl" case mojolicious = "mojolicious" case monkey = "monkey" case moonscript = "moonscript" case n1ql = "n1ql" case nginx = "nginx" case nimrod = "nimrod" case nix = "nix" case nsis = "nsis" case objectivec = "objectivec" case ocaml = "ocaml" case openscad = "openscad" case oxygene = "oxygene" case parser3 = "parser3" case pf = "pf" case pgsql = "pgsql" case php = "php" case plaintext = "plaintext" case pony = "pony" case powershell = "powershell" case processing = "processing" case profile = "profile" case prolog = "prolog" case properties = "properties" case protobuf = "protobuf" case puppet = "puppet" case purebasic = "purebasic" case python = "python" case q = "q" case qml = "qml" case r = "r" case reasonml = "reasonml" case rib = "rib" case roboconf = "roboconf" case routeros = "routeros" case rsl = "rsl" case ruleslanguage = "ruleslanguage" case rust = "rust" case sas = "sas" case scala = "scala" case scheme = "scheme" case scilab = "scilab" case scss = "scss" case shell = "shell" case smali = "smali" case smalltalk = "smalltalk" case sml = "sml" case sqf = "sqf" case sql = "sql" case stan = "stan" case stata = "stata" case step21 = "step21" case stylus = "stylus" case subunit = "subunit" case swift = "swift" case taggerscript = "taggerscript" case yaml = "yaml" case tap = "tap" case tcl = "tcl" case tex = "tex" case thrift = "thrift" case tp = "tp" case twig = "twig" case typescript = "typescript" case vala = "vala" case vbnet = "vbnet" case vbscript = "vbscript" case vbscriptHtml = "vbscript-html" case verilog = "verilog" case vhdl = "vhdl" case vim = "vim" case x86asm = "x86asm" case xl = "xl" case xquery = "xquery" case zephir = "zephir" } } ================================================ FILE: DevToys/DevToys/Component/ControlBackgroundLayer.swift ================================================ // // ControlBackgroundLayer.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil final class ControlBackgroundLayer: CALoadLayer { func update() { self.backgroundColor = R.Color.controlBackgroundColor.cgColor } override func onAwake() { self.cornerRadius = R.Size.corner self.areAnimationsEnabled = false } } final class ControlErrorBackgroundLayer: CALoadLayer { func update(isError: Bool) { if isError { self.backgroundColor = NSColor.systemRed.withAlphaComponent(0.5).cgColor } else { self.backgroundColor = R.Color.controlBackgroundColor.cgColor } } override func onAwake() { self.cornerRadius = R.Size.corner self.areAnimationsEnabled = false } } final class ControlButtonBackgroundLayer: CALoadLayer { func update(isHighlighted: Bool) { self.areAnimationsEnabled = true defer { self.areAnimationsEnabled = false } if isHighlighted { self.backgroundColor = R.Color.controlHighlightedBackgroundColor.cgColor } else { self.backgroundColor = R.Color.controlBackgroundColor.cgColor } } override func onAwake() { self.cornerRadius = R.Size.corner self.areAnimationsEnabled = false } } ================================================ FILE: DevToys/DevToys/Component/ControlContainer.swift ================================================ // // ComponentBaseView.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil final class ControlArea: NSLoadView { var control: NSView? { didSet { oldValue?.removeFromSuperview() if let control = control { self.stackView.addArrangedSubview(control) } } } var icon: NSImage? { get { iconView.image } set { iconView.image = newValue; iconView.isHidden = icon == nil } } var title: String { get { titleLabel.stringValue } set { titleLabel.stringValue = newValue } } var message: String? = "Message" { didSet { messageLabel.isHidden = message == nil; messageLabel.stringValue = message ?? "" } } convenience init(icon: NSImage? = nil, title: String, message: String? = nil, control: NSView) { self.init() self.setup(icon: icon, title: title, message: message, control: control) } private func setup(icon: NSImage?, title: String, message: String?, control: NSView) { self.icon = icon self.title = title self.message = message self.control = control } private let iconView = NSImageView() private let titleLabel = NSTextField(labelWithString: "Title") private let messageLabel = NSTextField(labelWithString: "Message") private let titleStack = NSStackView() private let stackView = NSStackView() private let backgroundLayer = ControlBackgroundLayer.animationDisabled() override func updateLayer() { self.backgroundLayer.update() } override func layout() { super.layout() self.backgroundLayer.frame = bounds } override func onAwake() { self.wantsLayer = true self.layer?.addSublayer(backgroundLayer) self.snp.makeConstraints{ make in make.height.equalTo(64) } self.addSubview(stackView) self.stackView.orientation = .horizontal self.stackView.edgeInsets = .init(x: 16, y: 0) self.stackView.spacing = 16 self.stackView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.stackView.addArrangedSubview(iconView) self.iconView.image = R.Image.ToolList.base64Coder self.iconView.snp.makeConstraints{ make in make.size.equalTo(24) } self.stackView.addArrangedSubview(titleStack) self.titleStack.orientation = .vertical self.titleStack.spacing = 4 self.titleStack.alignment = .left self.titleStack.addArrangedSubview(titleLabel) self.titleLabel.font = NSFont.systemFont(ofSize: R.Size.controlTitleFontSize) self.titleStack.addArrangedSubview(messageLabel) self.messageLabel.font = NSFont.systemFont(ofSize: R.Size.controlFontSize) self.messageLabel.textColor = .secondaryLabelColor self.stackView.addArrangedSubview(NSView()) } } ================================================ FILE: DevToys/DevToys/Component/DatePicker.swift ================================================ // // DatePicker.swift // DevToys // // Created by yuki on 2022/02/04. // import CoreUtil final class DatePicker: NSLoadView { var date: Date { get { datePicker.dateValue } set { datePicker.dateValue = newValue } } var datePublisher: AnyPublisher { datePicker.actionPublisher.map{ self.datePicker.dateValue }.eraseToAnyPublisher() } private let datePicker = NSDatePicker() private let backgroundLayer = ControlBackgroundLayer.animationDisabled() override func updateLayer() { self.backgroundLayer.update() } override func layout() { super.layout() self.backgroundLayer.frame = bounds } override func onAwake() { self.wantsLayer = true self.layer?.addSublayer(backgroundLayer) self.snp.makeConstraints{ make in make.height.equalTo(R.Size.controlHeight) } self.addSubview(datePicker) self.datePicker.isBezeled = false self.datePicker.font = .systemFont(ofSize: 14) self.datePicker.snp.makeConstraints{ make in make.centerY.equalToSuperview() make.left.right.equalToSuperview().inset(4) } } } ================================================ FILE: DevToys/DevToys/Component/DragImageView.swift ================================================ // // ImageBoxView.swift // DevToys // // Created by yuki on 2022/02/25. // import CoreUtil import AppKit protocol DragImageViewDelegate: NSObjectProtocol { func dragImageView(_ dragImageView: DragImageView, draggingFilenameFor image: NSImage) -> String } final class DragImageViewBlockDelegate: NSObject, DragImageViewDelegate { private let filename: () -> String init(_ filename: @escaping () -> String) { self.filename = filename } func dragImageView(_ dragImageView: DragImageView, draggingFilenameFor image: NSImage) -> String { filename() } } final class DragImageView: NSLoadImageView, NSDraggingSource { weak var delegate: DragImageViewDelegate? private var mouseDownLocation: CGPoint? private static let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent("DragDropImageView") => { try? FileManager.default.createDirectory(at: $0, withIntermediateDirectories: true, attributes: nil) } override func onAwake() { self.isEnabled = true } func draggingSession(_: NSDraggingSession, sourceOperationMaskFor _: NSDraggingContext) -> NSDragOperation { return NSDragOperation.copy } func draggingSession(_: NSDraggingSession, endedAt _: NSPoint, operation: NSDragOperation) {} override func mouseDown(with event: NSEvent) { self.mouseDownLocation = event.location(in: self) } override func mouseDragged(with event: NSEvent) { guard let delegate = self.delegate else { return } guard let mouseDownLocation = mouseDownLocation else { return } let dragPoint = event.locationInWindow let dragDistance = hypot(mouseDownLocation.x - dragPoint.x, mouseDownLocation.y - dragPoint.y) if dragDistance < 3 { return } guard let image = self.image else { return } let filename = delegate.dragImageView(self, draggingFilenameFor: image) let imageURL = Self.temporaryDirectory.appendingPathComponent(filename) try! image.tiffRepresentation?.write(to: imageURL) let draggingItem = NSDraggingItem(pasteboardWriter: imageURL as NSURL) let draggingFrame = bounds draggingItem.draggingFrame = draggingFrame draggingItem.imageComponentsProvider = { let component = NSDraggingImageComponent(key: NSDraggingItem.ImageComponentKey.icon) component.contents = image component.frame = draggingFrame return [component] } beginDraggingSession(with: [draggingItem], event: event, source: self) } } ================================================ FILE: DevToys/DevToys/Component/EmptyImageTableView.swift ================================================ // // EmptyImageTableView.swift // DevToys // // Created by yuki on 2022/02/22. // import CoreUtil open class EmptyImageTableView: NSLoadTableView { open var emptyView: NSView? { get { emptyViewPlaceholder.contentView } set { emptyViewPlaceholder.contentView = newValue } } private let emptyViewPlaceholder = NSPlaceholderView() open override func didRemove(_ rowView: NSTableRowView, forRow row: Int) { super.didRemove(rowView, forRow: row) self.updateRowCount() } open override func didAdd(_ rowView: NSTableRowView, forRow row: Int) { super.didAdd(rowView, forRow: row) self.updateRowCount() } private func updateRowCount() { if numberOfRows == 0 { self.emptyViewPlaceholder.isHidden = false } else { self.emptyViewPlaceholder.isHidden = true } } private func commonInit() { self.addSubview(emptyViewPlaceholder) self.emptyViewPlaceholder.snp.makeConstraints{ make in make.center.equalToSuperview() } self.updateRowCount() } public override init(frame frameRect: NSRect) { super.init(frame: frameRect) self.commonInit() } public required init?(coder: NSCoder) { super.init(coder: coder) self.commonInit() } } extension EmptyImageTableView { public func setFileDropEmptyView(_ title: String = "Drop Files Here".localized()) { self.emptyView = DropIndicatorView(title: title) } } final class DropIndicatorView: NSLoadStackView { private let imageView = NSImageView(image: R.Image.drop) private let titleLabel = NSTextField(labelWithString: "Title") convenience init(title: String) { self.init() self.titleLabel.stringValue = title } override func onAwake() { self.unregisterDraggedTypes() self.alignment = .centerX self.orientation = .vertical self.addArrangedSubview(imageView) self.addArrangedSubview(titleLabel) } } ================================================ FILE: DevToys/DevToys/Component/FileDrop.swift ================================================ // // FileDrop.swift // DevToys // // Created by yuki on 2022/02/02. // import CoreUtil final class FileDropSection: Section { var urlsPublisher: AnyPublisher<[URL], Never> { fileDrop.urlsPublisher.merge(with: openButton.urlPublisher.map{ [$0] }).eraseToAnyPublisher() } private let fileDrop = FileDrop() private let openButton = OpenSectionButton(title: "Open".localized(), image: R.Image.open) override func onAwake() { super.onAwake() self.title = "File".localized() self.addToolbarItem(openButton) self.addStackItem(fileDrop) self.snp.makeConstraints{ make in make.height.equalTo(160) } } } final class FileDrop: NSLoadView { let urlsPublisher = PassthroughSubject<[URL], Never>() private let backgroundLayer = ControlBackgroundLayer.animationDisabled() private let imageView = NSImageView(image: R.Image.drop) private let titleLabel = NSTextField(labelWithString: "Drop Files Here".localized()) private let stackView = NSStackView() override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { sender.draggingPasteboard.canReadTypes([.fileURL]) ? .copy : .none } override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { guard let urls = sender.draggingPasteboard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL] else { return false } urlsPublisher.send(urls) return true } override func layout() { super.layout() self.backgroundLayer.frame = bounds } override func updateLayer() { backgroundLayer.update() } override func onAwake() { self.wantsLayer = true self.layer?.addSublayer(backgroundLayer) self.addSubview(stackView) self.stackView.orientation = .vertical self.stackView.snp.makeConstraints{ make in make.center.equalToSuperview() } self.stackView.addArrangedSubview(imageView) self.stackView.addArrangedSubview(titleLabel) self.registerForDraggedTypes([.fileURL]) } } ================================================ FILE: DevToys/DevToys/Component/ImageDropView.swift ================================================ // // ImageDropView.swift // DevToys // // Created by yuki on 2022/02/28. // import CoreUtil class ImageDropView: NSLoadView { var image: NSImage? { get { imageView.image } set { self.imageView.image = newValue self.dropIndicator.isHidden = newValue != nil } } let imagePublisher = PassthroughSubject() let imageView = NSImageView() private let backgroundLayer = ControlBackgroundLayer.animationDisabled() private let dropIndicator = DropIndicatorView(title: "Drop Image Here") override func layout() { super.layout() self.backgroundLayer.frame = bounds } override func updateLayer() { self.backgroundLayer.update() } override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { return .copy } override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { let images = ImageDropper.images(fromPasteboard: sender.draggingPasteboard) guard let image = images.first else { return false } imagePublisher.send(image) return true } override func onAwake() { self.imageView.unregisterDraggedTypes() self.registerForDraggedTypes([.fileURL, .URL, .fileContents]) self.wantsLayer = true self.layer?.addSublayer(backgroundLayer) self.addSubview(imageView) self.imageView.snp.makeConstraints{ make in make.width.equalTo(180) make.top.bottom.equalToSuperview().inset(16) make.centerX.equalToSuperview() } self.addSubview(dropIndicator) self.dropIndicator.snp.makeConstraints{ make in make.center.equalToSuperview() } } } ================================================ FILE: DevToys/DevToys/Component/NumberField.swift ================================================ // // NumberField.swift // DevToys // // Created by yuki on 2022/02/01. // import CoreUtil import CoreGraphics final class NumberField: NSLoadView { var value: CGFloat = 0 { didSet { textField.stringValue = value.formattedString() } } var valuePublisher: AnyPublisher, Never> { self.valueSubject.eraseToAnyPublisher() } var isEnabled: Bool { get { textField.isEnabled } set { textField.isEnabled = newValue } } var showStepper = true { didSet { self.stepper.isHidden = !showStepper } } private let stepper = NumberFieldStepper() private let textField = CustomFocusRingTextField() private let backgroundLayer = ControlBackgroundLayer.animationDisabled() private let valueSubject = PassthroughSubject, Never>() convenience init(autoWidth: Void) { self.init(width: 100) } convenience init(width: CGFloat) { self.init() self.snp.makeConstraints{ make in make.width.equalTo(width) } } override func layout() { self.backgroundLayer.frame = bounds let textFieldFrame = CGRect(originX: 8, centerY: bounds.midY, size: [bounds.width - 28, textField.intrinsicContentSize.height]) let focusRingBounds = CGRect(origin: bounds.origin - textFieldFrame.origin, size: bounds.size) self.textField.focusRingBounds = focusRingBounds self.textField.frame = textFieldFrame super.layout() } override func updateLayer() { self.backgroundLayer.update() } private func receiveString(_ string: String) { guard let value = Double(string) else { self.textField.stringValue = "0" return NSSound.beep() } self.valueSubject.send(.to(value)) } override func onAwake() { self.snp.makeConstraints{ make in make.height.equalTo(R.Size.controlHeight) } self.wantsLayer = true self.layer?.cornerRadius = R.Size.corner self.layer?.addSublayer(backgroundLayer) self.addSubview(stepper) self.stepper.snp.makeConstraints{ make in make.right.top.bottom.equalToSuperview() } self.addSubview(textField) self.textField.font = .systemFont(ofSize: 12) self.textField.stringValue = "0" self.textField.changeStringPublisher .sink{[unowned self] in self.receiveString($0) }.store(in: &objectBag) self.stepper.upPublisher .sink{[unowned self] in self.valueSubject.send(.by(1)) }.store(in: &objectBag) self.stepper.downPublisher .sink{[unowned self] in self.valueSubject.send(.by(-1)) }.store(in: &objectBag) } } final private class NumberFieldStepper: NSLoadControl { let upPublisher = PassthroughSubject() let downPublisher = PassthroughSubject() override var isFlipped: Bool { true } private let backgroundLayer = CALayer.animationDisabled() private let upBackgroundLayer = CALayer.animationDisabled() private let downBackgroundLayer = CALayer.animationDisabled() private let upImageView = NSImageView(image: R.Image.stepperUp) private let downImageView = NSImageView(image: R.Image.stepperDown) private var mouseDownPosition: Position? { didSet { needsDisplay = true } } private var highlightPosition: Position? { didSet { needsDisplay = true } } private enum Position { case up, down } private func position(of location: CGPoint) -> Position { if location.y > bounds.height/2 { return .down } else { return .up } } override func mouseDown(with event: NSEvent) { self.mouseDownPosition = position(of: event.location(in: self)) self.highlightPosition = position(of: event.location(in: self)) Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { timer in timer.invalidate() Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in guard let mouseDownPosition = self.mouseDownPosition else { return timer.invalidate() } self.sendPosition(mouseDownPosition) NSHapticFeedbackManager.defaultPerformer.perform(.levelChange, performanceTime: .now) } } } override func mouseDragged(with event: NSEvent) { if position(of: event.location(in: self)) == mouseDownPosition { self.highlightPosition = mouseDownPosition } else { self.highlightPosition = nil } } override func mouseUp(with event: NSEvent) { defer { self.highlightPosition = nil self.mouseDownPosition = nil } if let position = mouseDownPosition, self.position(of: event.location(in: self)) == position { sendPosition(position) } } private func sendPosition(_ position: Position) { switch position { case .up: upPublisher.send() case .down: downPublisher.send() } } override func layout() { super.layout() self.backgroundLayer.frame = bounds self.upBackgroundLayer.frame = CGRect(origin: [0, 0], size: [bounds.width, bounds.height/2]) self.downBackgroundLayer.frame = CGRect(origin: [0, bounds.height/2], size: [bounds.width, bounds.height/2]) self.upImageView.frame.center = upBackgroundLayer.frame.center self.downImageView.frame.center = downBackgroundLayer.frame.center } override func updateLayer() { self.backgroundLayer.backgroundColor = R.Color.controlHighlightedBackgroundColor.cgColor if highlightPosition == .up { self.upBackgroundLayer.backgroundColor = R.Color.controlBackgroundColor.cgColor } else { self.upBackgroundLayer.backgroundColor = nil } if highlightPosition == .down { self.downBackgroundLayer.backgroundColor = R.Color.controlBackgroundColor.cgColor } else { self.downBackgroundLayer.backgroundColor = nil } } override func onAwake() { self.wantsLayer = true self.layer?.addSublayer(backgroundLayer) self.addSubview(upImageView) self.upImageView.frame.size = [10, 10] self.addSubview(downImageView) self.downImageView.frame.size = [10, 10] self.backgroundLayer.addSublayer(upBackgroundLayer) self.backgroundLayer.addSublayer(downBackgroundLayer) self.snp.makeConstraints{ make in make.width.equalTo(20) } } } ================================================ FILE: DevToys/DevToys/Component/Page.swift ================================================ // // ToolPage.swift // DevToys // // Created by yuki on 2022/01/30. // import CoreUtil class Page: NSLoadView { let stackView = NSStackView() let scrollView = NSScrollView() func addSection(_ section: NSView, fillWidth: Bool = true) { self.stackView.addArrangedSubview(section) if fillWidth { section.snp.makeConstraints{ make in make.right.left.equalToSuperview().inset(16) } } } @discardableResult func addSection2(_ stack1: NSView, _ stack2: NSView) -> NSStackView { let stackView = NSStackView() stackView.distribution = .fillEqually stackView.orientation = .horizontal stackView.alignment = .top stackView.addArrangedSubview(stack1) stackView.addArrangedSubview(stack2) self.addSection(stackView) return stackView } private func commonInit() { self.addSubview(scrollView) self.scrollView.contentView = FlipClipView() self.scrollView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.stackView.edgeInsets = NSEdgeInsets(x: 16, y: 16) self.stackView.orientation = .vertical self.stackView.alignment = .left self.scrollView.documentView = stackView self.stackView.snp.makeConstraints{ make in make.top.equalToSuperview() make.right.left.equalToSuperview() } } public override init(frame frameRect: NSRect) { super.init(frame: frameRect) commonInit() } public required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } } private class FlipClipView: NSClipView { override var isFlipped: Bool { true } } ================================================ FILE: DevToys/DevToys/Component/PopupButton.swift ================================================ // // PopupButton.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil protocol TextItem: CaseIterable, Equatable { var title: String { get } } extension RawRepresentable where RawValue == String { var title: String { rawValue } } final class EnumPopupButton: PopupButton { var selectedItem: Item? { didSet { selectedMenuTitle = selectedItem?.title } } let itemPublisher = PassthroughSubject() override func makeMenuItems() -> [NSMenuItem] { Item.allCases.map{ item in NSMenuItem(title: item.title, isSelected: selectedItem == item, action: { self.itemPublisher.send(item) }) } } } class PopupButton: NSLoadButton { var menuItems = [NSMenuItem]() var selectedMenuTitle: String? { didSet { self.titleLabel.stringValue = selectedMenuTitle ?? "No selection".localized() } } func makeMenuItems() -> [NSMenuItem] { menuItems } private let titleLabel = NSTextField(labelWithString: "No selection".localized()) private let pulldownIndicator = NSImageView(image: R.Image.pulldownIndicator) private let stackView = NSStackView() private let backgroundLayer = ControlBackgroundLayer.animationDisabled() override var isHighlighted: Bool { didSet { needsDisplay = true } } override func updateLayer() { self.backgroundLayer.update() } override func layout() { super.layout() self.backgroundLayer.frame = bounds } override func mouseDown(with event: NSEvent) { let menu = NSMenu() for item in makeMenuItems() { menu.addItem(item) } menu.minimumWidth = self.bounds.width menu.popUp(positioning: menu.items.first(where: { $0.isSelected }), at: .zero, in: self) } override func onAwake() { self.wantsLayer = true self.isBordered = false self.title = "" self.layer?.addSublayer(backgroundLayer) self.snp.makeConstraints{ make in make.height.equalTo(R.Size.controlHeight) } self.addSubview(stackView) self.stackView.spacing = 4 self.stackView.edgeInsets = .init(x: 8, y: 0) self.stackView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.titleLabel.font = .systemFont(ofSize: R.Size.controlTitleFontSize) self.stackView.addArrangedSubview(titleLabel) self.stackView.addArrangedSubview(pulldownIndicator) self.pulldownIndicator.snp.makeConstraints{ make in make.size.equalTo(18) } } } ================================================ FILE: DevToys/DevToys/Component/RegexTextView.swift ================================================ // // RegexTextView.swift // DevToys // // Created by yuki on 2022/02/03. // import CoreUtil final class RegexTextView: NSLoadView { var string: String { get { textView.string } set { textView.string = newValue } } var highlightRanges = [NSRange]() { didSet { updateHighlights() } } var stringPublisher: AnyPublisher { textView.stringPublisher.eraseToAnyPublisher() } var isEditable: Bool { get { textView.isEditable } set { textView.isEditable = newValue } } override var isSelectable: Bool { get { textView.isSelectable } set { textView.isSelectable = newValue } } private let scrollView = _TextView.scrollableTextView() private lazy var textView = scrollView.documentView as! _TextView private func updateHighlights() { guard let textStorage = textView.textStorage else { return } objc_try { textStorage.removeAttribute(NSAttributedString.Key.backgroundColor, range: NSRange(location: 0, length: textStorage.length)) for range in highlightRanges { textStorage.addAttribute(NSAttributedString.Key.backgroundColor, value: NSColor.systemBlue.withAlphaComponent(0.5), range: range) } } catch: { error in print(error) } } override func onAwake() { self.wantsLayer = true self.layer?.cornerRadius = R.Size.corner self.addSubview(scrollView) self.textView.allowsUndo = true self.textView.font = .monospacedSystemFont(ofSize: R.Size.codeFontSize, weight: .regular) self.textView.backgroundColor = .quaternaryLabelColor self.textView.textContainerInset = [0, 4] self.scrollView.snp.makeConstraints{ make in make.edges.equalToSuperview() } } } final private class _TextView: NSTextView { let stringPublisher = PassthroughSubject() override func didChangeText() { self.stringPublisher.send(string) } } ================================================ FILE: DevToys/DevToys/Component/Section.swift ================================================ // // ControlStack.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil class Section: NSLoadView { var title: String { get { titleLabel.stringValue } set { titleLabel.stringValue = newValue } } var minTitle: Bool = false { didSet { self.titleStackView.snp.updateConstraints{ make in make.height.equalTo(minTitle ? 16 : R.Size.controlHeight) } } } var orientation: NSUserInterfaceLayoutOrientation { get { contentStackView.orientation } set { contentStackView.orientation = newValue } } func addStackItem(_ item: NSView, fillWidth: Bool = true) { self.contentStackView.addArrangedSubview(item) if fillWidth { item.snp.makeConstraints{ make in make.right.left.equalToSuperview() } } } func addToolbarItem(_ item: NSView) { self.titleStackView.addArrangedSubview(item) } func removeToolbarItem(_ item: NSView) { self.titleStackView.removeArrangedSubview(item) } func removeAllToolbarItem() { self.titleStackView.subviews.removeAll(where: { $0 !== titleLabel && $0 !== spacer }) } convenience init(title: String, orientation: NSUserInterfaceLayoutOrientation = .vertical, fillWidth: Bool = true, items: [NSView] = [], toolbarItems: [NSView] = []) { self.init() self.title = title self.orientation = orientation for item in items { self.addStackItem(item, fillWidth: fillWidth) } for toolbarItem in toolbarItems { self.addToolbarItem(toolbarItem) } } private let titleLabel = NSTextField(labelWithString: "Title") private let spacer = NSView() private let titleStackView = NSStackView() private let contentStackView = NSStackView() override func onAwake() { self.addSubview(titleStackView) self.titleStackView.orientation = .horizontal self.titleStackView.distribution = .fillProportionally self.titleStackView.snp.makeConstraints{ make in make.left.right.top.equalToSuperview() make.height.equalTo(R.Size.controlHeight) } self.addSubview(contentStackView) self.contentStackView.distribution = .fillEqually self.contentStackView.orientation = .vertical self.contentStackView.snp.makeConstraints{ make in make.bottom.right.left.equalToSuperview() make.top.equalTo(titleStackView.snp.bottom).offset(8) } self.titleStackView.addArrangedSubview(titleLabel) self.titleStackView.addArrangedSubview(spacer) self.titleLabel.font = .systemFont(ofSize: R.Size.controlTitleFontSize) } } ================================================ FILE: DevToys/DevToys/Component/SectionButton+.swift ================================================ // // SectionButton+.swift // DevToys // // Created by yuki on 2022/01/30. // import CoreUtil final class SearchSectionButton: SectionButton { private let selector = NSSelectorFromString("performFindPanelAction") weak var textView: TextViewType? override func onAwake() { super.onAwake() self.image = R.Image.search self.actionPublisher .sink{[unowned self] in guard let textView = textView else { return assertionFailure() } textView.becomeFocused() guard let findMenu = NSApp.mainMenu?.item(withTag: 1001)?.submenu?.item(withTag: 1002)?.submenu else { return } let index = findMenu.indexOfItem(withTag: 1) findMenu.performActionForItem(at: index) } .store(in: &objectBag) } } final class OpenSectionButton: SectionButton { let urlPublisher = PassthroughSubject() var fileStringPublisher: AnyPublisher { urlPublisher.compactMap{ try? String(contentsOf: $0) }.eraseToAnyPublisher() } var fileDataPublisher: AnyPublisher { urlPublisher.compactMap{ try? Data(contentsOf: $0) }.eraseToAnyPublisher() } @objc private func buttonAction(_: Any) { let panel = NSOpenPanel() guard panel.runModal() == .OK, let url = panel.url else { return } self.urlPublisher.send(url) } override func onAwake() { super.onAwake() self.toolTip = "Open".localized() self.title = "" self.image = R.Image.open self.setTarget(self, action: #selector(buttonAction)) } } final class CopySectionButton: SectionButton { var stringContent: String? convenience init(hasTitle: Bool) { self.init() if hasTitle { self.title = "Copy".localized() } else { self.title = "" } } override func onAwake() { super.onAwake() self.image = R.Image.copy self.toolTip = "Copy".localized() self.actionPublisher .sink{[unowned self] in guard let stringContent = self.stringContent else { return NSSound.beep() } NSPasteboard.general.prepareForNewContents(with: .none) NSPasteboard.general.setString(stringContent, forType: .string) Toast.show(message: "Copied!".localized()) } .store(in: &objectBag) } } final class PasteSectionButton: SectionButton { var stringPublisher: AnyPublisher { self.actionPublisher .peek{ Toast.show(message: "Pasted!".localized()) } .map{ return NSPasteboard.general.string(forType: .string) } .eraseToAnyPublisher() } override func onAwake() { super.onAwake() self.toolTip = "Paste".localized() self.title = "Paste".localized() self.image = R.Image.paste } } ================================================ FILE: DevToys/DevToys/Component/SectionButton.swift ================================================ // // ControlStackButton.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil class SectionButton: NSLoadButton { override var title: String { didSet { updateTitle() } } override var image: NSImage? { didSet { iconView.image = image } } private let titleLabel = NSTextField(labelWithString: "Paste".localized()) private let iconView = NSImageView(image: R.Image.Tool.convert) private let stackView = NSStackView() private let backgroundLayer = ControlButtonBackgroundLayer.animationDisabled() override var isHighlighted: Bool { didSet { needsDisplay = true } } private func updateTitle() { titleLabel.stringValue = title if title.isEmpty { titleLabel.isHidden = true self.stackView.distribution = .fillEqually } else { titleLabel.isHidden = false } } override func updateLayer() { self.backgroundLayer.update(isHighlighted: isHighlighted) } override func draw(_ dirtyRect: NSRect) { updateLayer() } override func layout() { super.layout() self.backgroundLayer.frame = bounds } override func onAwake() { self.snp.makeConstraints{ make in make.height.equalTo(R.Size.controlHeight) } self.wantsLayer = true self.isBordered = false self.title = "" self.layer?.addSublayer(backgroundLayer) self.snp.makeConstraints{ make in make.height.equalTo(R.Size.controlHeight) } self.addSubview(stackView) self.stackView.spacing = 4 self.stackView.distribution = .equalCentering self.stackView.edgeInsets = .init(x: 8, y: 0) self.stackView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.stackView.addArrangedSubview(iconView) self.iconView.snp.makeConstraints{ make in make.size.equalTo(20) } self.titleLabel.font = .systemFont(ofSize: R.Size.controlTitleFontSize) self.stackView.addArrangedSubview(titleLabel) self.updateTitle() } } extension NSButton { convenience init(title: String? = nil, image: NSImage? = nil) { self.init() self.setup(title: title, image: image) } private func setup(title: String?, image: NSImage?) { self.title = title ?? "" self.image = image } } ================================================ FILE: DevToys/DevToys/Component/TagCloudView.swift ================================================ // // TagCloudView.swift // DevToys // // Created by yuki on 2022/02/01. // import CoreUtil final class TagCloudView: NSLoadView { let collectionView = NSCollectionView() let flowLayout = LeftAlignedCollectionViewFlowLayout() let buttonPublisher = PassthroughSubject() let selectPublisher = PassthroughSubject() var selectedItem: Int? { didSet { if let selectedItem = selectedItem { collectionView.selectItems(at: [IndexPath(item: selectedItem, section: 0)], scrollPosition: .bottom) } else { collectionView.deselectAll(nil) } } } override var isSelectable: Bool { get { collectionView.isSelectable } set { collectionView.isSelectable = newValue; collectionView.reloadData() } } var items: [String] = ["Hello", "World"] { didSet { collectionView.reloadData() DispatchQueue.main.async {[self] in self.reloadHeight() } } } override func layout() { self.reloadHeight() super.layout() } private func reloadHeight() { let collectionViewContentSize = flowLayout.collectionViewContentSize self.snp.remakeConstraints{ make in make.height.equalTo(collectionViewContentSize.height) } } override func onAwake() { self.frame.size = [1, 1] flowLayout.scrollDirection = .vertical flowLayout.estimatedItemSize = .zero flowLayout.minimumInteritemSpacing = 10 flowLayout.minimumLineSpacing = 10 flowLayout.invalidateLayout() collectionView.collectionViewLayout = flowLayout self.addSubview(collectionView) self.collectionView.dataSource = self self.collectionView.delegate = self self.collectionView.register(TagCloudItem.self, forItemWithIdentifier: TagCloudItem.identifier) self.collectionView.snp.makeConstraints{ make in make.edges.equalToSuperview() } } } extension TagCloudView: NSCollectionViewDelegateFlowLayout, NSCollectionViewDataSource { func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { items.count } func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set) { guard let indexPath = indexPaths.first else { return } selectPublisher.send(indexPath.item) } func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { let item = collectionView.makeItem(withIdentifier: TagCloudItem.identifier, for: indexPath) as! TagCloudItem item.cell.isEnabled = !isSelectable item.cell.actionPublisher .sink{ self.buttonPublisher.send(indexPath.item) }.store(in: &item.objectBag) item.cell.label.stringValue = items[indexPath.item] return item } func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize { let label = NSTextField(labelWithString: items[indexPath.item]) label.font = .systemFont(ofSize: 14) label.sizeToFit() return label.frame.size + [16, 12] } } final private class TagCloudItem: NSCollectionViewItem { static let identifier = NSUserInterfaceItemIdentifier(rawValue: "TagCloudItem") override var isSelected: Bool { didSet { cell.isSelected = isSelected } } class Cell: NSLoadButton { let label = NSTextField(labelWithString: "") let backgroundLayer = ControlButtonBackgroundLayer.animationDisabled() var isSelected: Bool = false { didSet { needsDisplay = true } } override func layout() { super.layout() self.backgroundLayer.frame = bounds } override func updateLayer() { backgroundLayer.update(isHighlighted: isHighlighted) backgroundLayer.areAnimationsEnabled = true if isSelected { backgroundLayer.backgroundColor = NSColor.controlAccentColor.withAlphaComponent(0.5).cgColor backgroundLayer.borderColor = NSColor.controlAccentColor.cgColor backgroundLayer.borderWidth = 1 } else { backgroundLayer.borderWidth = 0 } backgroundLayer.areAnimationsEnabled = false } override func onAwake() { self.wantsLayer = true self.title = "" self.isBordered = false self.layer?.addSublayer(backgroundLayer) self.addSubview(label) self.label.alignment = .center self.label.snp.makeConstraints{ make in make.left.right.equalToSuperview().inset(8) make.centerY.equalToSuperview() } } } let cell = Cell() override func loadView() { self.view = cell } } final class LeftAlignedCollectionViewFlowLayout: NSCollectionViewFlowLayout { override func layoutAttributesForElements(in rect: NSRect) -> [NSCollectionViewLayoutAttributes] { let attributes = super.layoutAttributesForElements(in: rect) var leftMargin = sectionInset.left var maxY: CGFloat = -1.0 attributes.forEach { layoutAttribute in if layoutAttribute.representedElementCategory == .item { if layoutAttribute.frame.origin.y >= maxY { leftMargin = sectionInset.left } layoutAttribute.frame.origin.x = leftMargin leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing maxY = max(layoutAttribute.frame.maxY, maxY) } } return attributes } } ================================================ FILE: DevToys/DevToys/Component/TextField.swift ================================================ // // TextField.swift // DevToys // // Created by yuki on 2022/01/30. // import CoreUtil final class TextField: NSLoadView { var string: String { get { textField.textField.stringValue } set { textField.textField.stringValue = newValue; copyButton.stringContent = newValue } } var placeholder: String? { get { textField.textField.placeholderString } set { textField.textField.placeholderString = newValue } } var changeStringPublisher: AnyPublisher { textField.textField.changeStringPublisher } var endEditingStringPublisher: AnyPublisher { textField.textField.endEditingStringPublisher } var isError: Bool { get { textField.isError } set { textField.isError = newValue } } var font: NSFont? { get { textField.textField.font } set { textField.textField.font = newValue } } var showCopyButton = false { didSet { copyButton.isHidden = !showCopyButton } } var isEditable: Bool = true { didSet { textField.textField.isEditable = isEditable } } convenience init(showCopyButton: Bool) { self.init() self.setup(showCopyButton: showCopyButton) } private func setup(showCopyButton: Bool) { self.showCopyButton = showCopyButton } private let textField = DotNetTextField() private let stackView = NSStackView() private let copyButton = CopySectionButton(hasTitle: false) override func onAwake() { self.snp.makeConstraints{ make in make.height.equalTo(R.Size.controlHeight) } self.addSubview(stackView) self.stackView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.stackView.addArrangedSubview(textField) self.textField.textField.lineBreakMode = .byTruncatingTail self.stackView.addArrangedSubview(copyButton) self.textField.snp.makeConstraints{ make in make.height.equalToSuperview() } } } final private class DotNetTextField: NSLoadView { let textField = CustomFocusRingTextField() let backgroundLayer = ControlErrorBackgroundLayer.animationDisabled() var isError = false { didSet { needsDisplay = true } } override func layout() { self.backgroundLayer.frame = bounds let textFieldFrame = CGRect(originX: 8, centerY: bounds.midY, size: [bounds.width - 16, textField.intrinsicContentSize.height]) let focusRingBounds = CGRect(origin: bounds.origin - textFieldFrame.origin, size: bounds.size) self.textField.focusRingBounds = focusRingBounds self.textField.frame = textFieldFrame super.layout() } override func updateLayer() { self.backgroundLayer.update(isError: isError) } override func onAwake() { self.wantsLayer = true self.layer?.addSublayer(backgroundLayer) self.addSubview(textField) self.textField.font = .systemFont(ofSize: 12) } } final class CustomFocusRingTextField: NSLoadTextField { var focusRingBounds: CGRect = .zero { didSet { self.noteFocusRingMaskChanged() } } override func drawFocusRingMask() { focusRingPath().fill() } override var focusRingMaskBounds: NSRect { focusRingBounds } public override func mouseDown(with event: NSEvent) { self.selectText(nil) } private func focusRingPath() -> NSBezierPath { NSBezierPath(roundedRect: focusRingBounds, xRadius: R.Size.corner, yRadius: R.Size.corner) } override func onAwake() { self.isBezeled = false self.isBordered = false self.bezelStyle = .roundedBezel self.drawsBackground = false self.backgroundColor = nil } } ================================================ FILE: DevToys/DevToys/Component/TextFieldSection.swift ================================================ // // TextFieldSection.swift // DevToys // // Created by yuki on 2022/02/01. // import CoreUtil struct TextFieldSectionOptions: OptionSet { let rawValue: UInt64 } final class TextFieldSection: Section { var string: String { get { textField.string } set { textField.string = newValue; copyButton.stringContent = newValue } } var stringPublisher: AnyPublisher { textField.changeStringPublisher.merge(with: self.pasteButton.stringPublisher.map{ $0 ?? "" }).eraseToAnyPublisher() } convenience init(title: String, isEditable: Bool) { self.init(title: title) self.textField.isEditable = isEditable if !isEditable { self.textField.showCopyButton = true self.minTitle = true self.removeAllToolbarItem() } } let textField = TextField(showCopyButton: false) private let pasteButton = PasteSectionButton() private let copyButton = CopySectionButton(hasTitle: false) override func onAwake() { super.onAwake() self.addStackItem(textField) self.addToolbarItem(pasteButton) self.addToolbarItem(copyButton) } } ================================================ FILE: DevToys/DevToys/Component/TextView.swift ================================================ // // TextView.swift // DevToys // // Created by yuki on 2022/02/03. // import CoreUtil final class TextView: NSLoadView { class _TextView: NSTextView { let stringPublisher = PassthroughSubject() var sendingValue = false override var string: String { get { super.string } set { if !sendingValue { super.string = newValue } } } override func didChangeText() { self.sendingValue = true stringPublisher.send(string) self.sendingValue = false } } var string: String { get { textView.string } set { textView.string = newValue } } var stringPublisher: AnyPublisher { textView.stringPublisher.eraseToAnyPublisher() } var isEditable: Bool { get { textView.isEditable } set { textView.isEditable = newValue } } override var isSelectable: Bool { get { textView.isSelectable } set { textView.isSelectable = newValue } } override func layout() { super.layout() self.backgroudLayer.frame = bounds } override func updateLayer() { self.backgroudLayer.update() } func becomeFocused() { self.window?.makeFirstResponder(self.textView) } private let scrollView = _TextView.scrollableTextView() private let backgroudLayer = ControlBackgroundLayer.animationDisabled() lazy var textView = scrollView.documentView as! _TextView override func onAwake() { self.wantsLayer = true self.layer?.addSublayer(backgroudLayer) self.addSubview(scrollView) self.textView.usesFindBar = true self.textView.allowsUndo = true self.textView.isAutomaticSpellingCorrectionEnabled = false self.textView.isAutomaticQuoteSubstitutionEnabled = false self.textView.isAutomaticDashSubstitutionEnabled = false self.textView.isAutomaticLinkDetectionEnabled = false self.textView.font = .monospacedSystemFont(ofSize: R.Size.codeFontSize, weight: .regular) self.textView.drawsBackground = false self.textView.textContainerInset = [0, 4] self.scrollView.snp.makeConstraints{ make in make.edges.equalToSuperview() } } } ================================================ FILE: DevToys/DevToys/Component/TextViewSection.swift ================================================ // // TextViewSection.swift // DevToys // // Created by yuki on 2022/02/01. // import CoreUtil struct TextSectionOptions: OptionSet { let rawValue: UInt64 static let inputable = TextSectionOptions(rawValue: 1 << 0) static let clearable = TextSectionOptions(rawValue: 1 << 1) static let pastable = TextSectionOptions(rawValue: 1 << 2) static let fileImportable = TextSectionOptions(rawValue: 1 << 3) static let outputable = TextSectionOptions(rawValue: 1 << 4) static let copyable = TextSectionOptions(rawValue: 1 << 5) static let searchable = TextSectionOptions(rawValue: 1 << 6) static let defaultInput = TextSectionOptions([.inputable, .clearable, .pastable, .fileImportable, .outputable, .searchable]) static let defaultOutput = TextSectionOptions([.outputable, .copyable, .searchable]) } protocol TextViewType: NSView { var string: String { get set } var stringPublisher: AnyPublisher { get } var isEditable: Bool { get set } var isSelectable: Bool { get set } func becomeFocused() } extension TextView: TextViewType {} extension CodeTextView: TextViewType {} final class TextViewSection: TextViewSectionBase {} final class CodeViewSection: TextViewSectionBase { var language: CodeTextView.Language = .json { didSet { textView.language = language } } convenience init(title: String, options: TextSectionOptions, language: CodeTextView.Language) { self.init(title: title, options: options) self.setup(language: language) } private func setup(language: CodeTextView.Language) { self.language = language } } class TextViewSectionBase: Section { var string = "" { didSet { self.textView.string = string self.copyButton.stringContent = string } } var stringPublisher: AnyPublisher { textView.stringPublisher.merge(with: pasteButton.stringPublisher.map{ $0 ?? "" }.merge(with: clearButton.actionPublisher.map{ "" }, openButton.fileStringPublisher)) .eraseToAnyPublisher() } var textSectionOptions = TextSectionOptions.defaultInput { didSet { updateToolbar() } } let textView = TextView() convenience init(title: String, options: TextSectionOptions) { self.init() self.title = title self.setup(options: options) } private func setup(options: TextSectionOptions) { self.textSectionOptions = options } private lazy var pasteButton = PasteSectionButton() private lazy var openButton = OpenSectionButton() private lazy var clearButton = SectionButton(image: R.Image.clear) private lazy var copyButton = CopySectionButton() private lazy var searchButton = SearchSectionButton() => { $0.textView = textView } private func updateToolbar() { self.textView.isEditable = textSectionOptions.contains(.inputable) self.textView.isSelectable = textSectionOptions.contains(.outputable) self.removeAllToolbarItem() if self.textSectionOptions.contains(.pastable) { self.addToolbarItem(pasteButton) } if self.textSectionOptions.contains(.copyable) { self.addToolbarItem(copyButton) } if self.textSectionOptions.contains(.fileImportable) { self.addToolbarItem(openButton) } if self.textSectionOptions.contains(.searchable) { self.addToolbarItem(searchButton) } } override func onAwake() { super.onAwake() self.updateToolbar() self.addStackItem(textView) } } ================================================ FILE: DevToys/DevToys/Component/Toast.swift ================================================ // // ACToast.swift // AxComponents // // Created by yuki on 2021/09/13. // Copyright © 2021 yuki. All rights reserved. // import Cocoa import CoreUtil import DequeModule import UserNotifications final public class Toast { public var message: String { get { toastWindow.toastView.message } set { toastWindow.toastView.message = newValue } } public var action: Action? { get { toastWindow.toastView.action } set { toastWindow.toastView.action = newValue } } public var color: NSColor? { get { toastWindow.toastView.color } set { toastWindow.toastView.color = newValue } } public var closeHandler: () -> () = {} public func addAttributeView(_ view: NSView, position: AttributeViewPosition) { toastWindow.toastView.addAttributeView(view, position: position) } public enum AttributeViewPosition { case left case right } private let toastWindow = ToastWindow() static var showingToast: Toast? static var pendingToasts = Deque() public convenience init(message: String, action: Action? = nil, color: NSColor? = nil) { self.init() self.message = message self.action = action self.color = color } public func show(with duration: TimeInterval = 2) { self.show{ handler in DispatchQueue.main.asyncAfter(deadline: .now()+duration) { handler() } } } public func show(with closeHandler: (@escaping () -> ()) -> ()) { if Toast.showingToast != nil { Toast.pendingToasts.append(self) return } Toast.showingToast = self self.toastWindow.show(with: closeHandler) { self.closeHandler() Toast.showingToast = nil guard let nextToast = Toast.pendingToasts.popFirst() else { return } nextToast.show() } } } extension Toast { public static func show(message: String, action: Action? = nil, with duration: TimeInterval = 2) { Toast(message: message, action: action).show(with: duration) } public static func debugLog(message: Any, action: Action? = nil, with duration: TimeInterval = 10) { #if DEBUG Toast(message: "\(message)", action: action).show(with: duration) #endif } public static func showError(message: String, error: Any) { #if DEBUG Toast(message: "\(error)", action: nil, color: .systemRed).show(with: 20) #else Toast(message: message, action: nil, color: .systemRed).show(with: 2) #endif } } final private class ToastWindow: NSPanel { let toastView = ToastView() func show(with closeHandler: (@escaping () -> ()) -> (), completion: @escaping () -> ()) { guard let screen = NSScreen.main else { return NSSound.beep() } self.layoutIfNeeded() let frame = CGRect(centerX: screen.frame.size.width / 2, originY: 120, size: self.frame.size) self.setFrame(frame, display: true) self.level = .floating self.appearance = NSAppearance(named: .darkAqua) self.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] self.orderFrontRegardless() self.alphaValue = 0 self.animator().alphaValue = 1 closeHandler { self.animator().alphaValue = 0 DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { self.close() completion() } } } init() { super.init(contentRect: .zero, styleMask: [.nonactivatingPanel, .fullSizeContentView], backing: .buffered, defer: true) self.contentView = toastView self.hasShadow = false self.backgroundColor = .clear } } final private class ToastView: NSLoadView { var message: String { get { textField.stringValue } set { textField.stringValue = newValue } } var action: Action? { didSet { reloadAction() } } var color: NSColor? { didSet { reloadColor() } } func addAttributeView(_ view: NSView, position: Toast.AttributeViewPosition) { switch position { case .right: stackView.addArrangedSubview(view) case .left: stackView.insertArrangedSubview(view, at: 0) } } private func reloadAction() { actionButton.isHidden = action == nil actionButton.title = action?.title ?? "" } private func reloadColor() { colorView.backgroundColor = color ?? .clear } private let stackView = NSStackView() private let textField = NSTextField(labelWithString: "") private let backgroundView = NSVisualEffectView() private let colorView = NSColorView() private let actionButton = ToastButton(title: "Search Google") convenience init(message: String) { self.init() self.textField.stringValue = message } @objc private func executeAction(_: Any) { action?.action() } override func onAwake() { self.wantsLayer = true self.layer?.cornerRadius = 10 self.snp.makeConstraints{ make in make.width.lessThanOrEqualTo(420) } self.addSubview(backgroundView) self.backgroundView.state = .active self.backgroundView.material = .sidebar self.backgroundView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.addSubview(colorView) self.colorView.alphaValue = 0.85 self.colorView.backgroundColor = .systemYellow self.colorView.snp.makeConstraints{ make in make.edges.equalToSuperview() } self.addSubview(stackView) self.stackView.snp.makeConstraints{ make in make.edges.equalToSuperview().inset(16) } self.stackView.addArrangedSubview(textField) self.textField.alignment = .center self.textField.lineBreakMode = .byWordWrapping self.textField.textColor = .white self.stackView.addArrangedSubview(actionButton) self.actionButton.bezelStyle = .inline self.actionButton.setTarget(self, action: #selector(executeAction)) self.reloadAction() } } final private class ToastButton: NSLoadButton { override var intrinsicContentSize: NSSize { super.intrinsicContentSize + [4, 4] } override func draw(_ dirtyRect: NSRect) { if isHighlighted { NSColor.black.withAlphaComponent(0.3).setFill() } else { NSColor.black.withAlphaComponent(0.2).setFill() } NSBezierPath(roundedRect: bounds, xRadius: bounds.height/2, yRadius: bounds.height/2).fill() let nsString = title as NSString nsString.draw(center: bounds, attributes: [ NSAttributedString.Key.foregroundColor : NSColor.secondaryLabelColor, NSAttributedString.Key.font : NSFont.systemFont(ofSize: NSFont.smallSystemFontSize, weight: .medium) ]) } override func onAwake() { self.bezelStyle = .inline } } extension NSString { public func draw(centerY rect: CGRect, attributes: [NSAttributedString.Key : Any]? = nil) { let actualRect = self.boundingRect(with: rect.size, options: [.usesLineFragmentOrigin], attributes: attributes) let originY = rect.minY + (rect.height - actualRect.height) / 2 let drawRect = CGRect(origin: [rect.minX, originY], size: actualRect.size) self.draw(with: drawRect, options: .usesLineFragmentOrigin, attributes: attributes) } public func drawRight(centerY rect: CGRect, attributes: [NSAttributedString.Key : Any]? = nil) { let actualRect = self.boundingRect(with: rect.size, options: [.usesLineFragmentOrigin], attributes: attributes) let originY = rect.minY + (rect.height - actualRect.height) / 2 var drawRect = CGRect(origin: [0, originY], size: actualRect.size) drawRect.end.x = rect.end.x self.draw(with: drawRect, options: .usesLineFragmentOrigin, attributes: attributes) } public func draw(center rect: CGRect, attributes: [NSAttributedString.Key : Any]? = nil) { let actualRect = self.boundingRect(with: rect.size, options: [.usesLineFragmentOrigin], attributes: attributes) let origin = (rect.size - actualRect.size).convertToPoint() / 2 let drawRect = CGRect(origin: origin, size: actualRect.size) self.draw(with: drawRect, options: .usesLineFragmentOrigin, attributes: attributes) } } extension Promise { public func peekProgressIndicator(_ message: String) -> Promise { Toast.showProgressIndicator(message: message, self) return self } public func sinkWithToast(_ successMessage: @escaping (Output) -> String) where Failure == Never { func neverhandler(_ output: T) -> String { "Never" } self.sinkWithToast(successMessage, neverhandler) } public func sinkWithToast(_ successMessage: @escaping (Output) -> String, _ failureMessage: @escaping (Failure) -> String) { let toast = Toast() self .receive(on: .main) .sink({ output in toast.color = nil toast.message = successMessage(output) toast.show() }, { error in toast.color = .systemRed toast.message = failureMessage(error) toast.show() }) } } extension Toast { fileprivate static func showProgressIndicator(message: String, _ promise: Promise) { let toast = Toast(message: message) let indicator = NSProgressIndicator() indicator.style = .spinning indicator.startAnimation(nil) indicator.snp.makeConstraints{ make in make.size.equalTo(16) } toast.addAttributeView(indicator, position: .right) toast.show(with: { close in promise.receive(on: .main).finally { close() } }) } } ================================================ FILE: DevToys/DevToys/DevToys.entitlements ================================================ com.apple.security.device.audio-input com.apple.security.device.camera ================================================ FILE: DevToys/DevToys/Info.plist ================================================ NSCameraUsageDescription Use camera CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName $(PRODUCT_NAME) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleVersion $(CURRENT_PROJECT_VERSION) LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSMainStoryboardFile Main NSPrincipalClass NSApplication SUFeedURL https://obuchiyuki.github.io/DevToysMac/sparkle/appcast.xml SUPublicEDKey DOsNciodUTmm6TgsE6uTJtoYBAZqvCjSzztuz+7I+4w= ================================================ FILE: DevToys/DevToys/Model/AppModel.swift ================================================ // // AppModel.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil extension NSViewController { var appModel: AppModel! { chainObject as? AppModel } } final class AppModel { @Observable var tool: Tool = .home { didSet { toolIdentifier = tool.identifier } } @RestorableState("app.toolIdentifier") var toolIdentifier = "" @Observable var searchQuery = "" let toolManager = ToolManager.shared let settings = Settings() init() { self.tool = toolManager.toolForIdentifier(toolIdentifier) ?? .home } } ================================================ FILE: DevToys/DevToys/Model/Settings.swift ================================================ // // Settings.swift // DevToys // // Created by yuki on 2022/02/16. // import CoreUtil final class Settings { enum AppearanceType: String { case useSystemSettings = "Use system setting" case lightMode = "Light mode" case darkMode = "Dark mode" } @RestorableState("appearance") var appearanceType: AppearanceType = .useSystemSettings } ================================================ FILE: DevToys/DevToys/Model/Tool+Default.swift ================================================ // // Tool+Default.swift // DevToys // // Created by yuki on 2022/02/13. // import CoreUtil extension Tool { static let home = Tool( title: "tool.home.title".localized(), identifier: "home", category: .home, icon: R.Image.Tool.home, toolDescription: "tool.home.description".localized(), showAlways: true, showOnHome: false, viewController: HomeViewController() ) static let search = Tool( title: "Search".localized(), identifier: "search", category: .home, icon: R.Image.Tool.home, toolDescription: "Search tools".localized(), showAlways: false, showOnHome: false, viewController: SearchViewController() ) static let settings = Tool( title: "Settings".localized(), identifier: "settings", category: .settings, icon: R.Image.Tool.settings, toolDescription: "Setting of application".localized(), showAlways: true, showOnHome: true, showOnSidebar: false, viewController: SettingViewController() ) // MARK: - Converters - static let jsonYamlConverter = Tool( title: "tool.jsonyaml.title".localized(), identifier: "jsonyaml", category: .converter, icon: R.Image.Tool.jsonConvert, sidebarTitle: "tool.jsonyaml.mintitle".localized(), toolDescription: "tool.jsonyaml.description".localized(), viewController: JSONYamlConverterViewController() ) static let numberBaseConverter = Tool( title: "tool.numbase.title".localized(), identifier: "numbase", category: .converter, icon: R.Image.Tool.numberBaseConvert, sidebarTitle: "tool.numbase.mintitle".localized(), toolDescription: "tool.numbase.description".localized(), viewController: NumberBaseConverterViewController() ) static let dateConverter = Tool( title: "tool.date.title".localized(), identifier: "date.converter", category: .converter, icon: R.Image.Tool.dateConvert, sidebarTitle: "tool.date.mintitle".localized(), toolDescription: "tool.date.description".localized(), viewController: DateConverterViewController() ) // MARK: - Encoder / Decoder - static let htmlCoder = Tool( title: "tool.html.title".localized(), identifier: "html.converter", category: .encoderDecoder, icon: R.Image.Tool.htmlCoder, sidebarTitle: "tool.html.mintitle".localized(), toolDescription: "tool.html.description".localized(), viewController: HTMLDecoderViewController() ) static let urlCoder = Tool( title: "tool.url.title".localized(), identifier: "url.converter", category: .encoderDecoder, icon: R.Image.Tool.urlCoder, sidebarTitle: "tool.url.mintitle".localized(), toolDescription: "tool.url.description".localized(), viewController: URLDecoderViewController() ) static let base64Coder = Tool( title: "tool.base64.title".localized(), identifier: "base64.converter", category: .encoderDecoder, icon: R.Image.Tool.base64Coder, sidebarTitle: "tool.base64.mintitle".localized(), toolDescription: "tool.base64.description".localized(), viewController: Base64DecoderViewController() ) static let jwtCoder = Tool( title: "tool.jwt.title".localized(), identifier: "jwt.converter", category: .encoderDecoder, icon: R.Image.Tool.jwtCoder, sidebarTitle: "tool.jwt.mintitle".localized(), toolDescription: "tool.jwt.description".localized(), viewController: JWTDecoderViewController() ) // MARK: - Formatter - static let jsonFormatter = Tool( title: "tool.jsonformat.title".localized(), identifier: "json.formatter", category: .formatter, icon: R.Image.Tool.jsonFormatter, sidebarTitle: "tool.jsonformat.mintitle".localized(), toolDescription: "tool.jsonformat.description".localized(), viewController: JSONFormatterViewController() ) static let xmlFormatter = Tool( title: "tool.xmlformat.title".localized(), identifier: "xml.formatter", category: .formatter, icon: R.Image.Tool.xmlFormatter, sidebarTitle: "tool.xmlformat.mintitle".localized(), toolDescription: "tool.xmlformat.description".localized(), viewController: XMLFormatterViewController() ) static let sqlFormatter = Tool( title: "SQL Formatter".localized(), identifier: "sql.formatter", category: .formatter, icon: R.Image.Tool.sqlFormatter, sidebarTitle: "SQL Formatter".localized(), toolDescription: "".localized(), viewController: SQLFormatterViewController() ) // MARK: - Generators - static let hashGenerator = Tool( title: "tool.hashgen.title".localized(), identifier: "hash.generator", category: .generator, icon: R.Image.Tool.hashGenerator, sidebarTitle: "tool.hashgen.mintitle".localized(), toolDescription: "tool.hashgen.description".localized(), viewController: HashGeneratorViewController() ) static let uuidGenerator = Tool( title: "tool.uuidgen.title".localized(), identifier: "uuid.generator", category: .generator, icon: R.Image.Tool.uuidGenerator, sidebarTitle: "tool.uuidgen.mintitle".localized(), toolDescription: "tool.uuidgen.description".localized(), viewController: UUIDGeneratorViewController() ) static let loremIpsumGenerator = Tool( title: "tool.ligen.title".localized(), identifier: "loremIpsum.generator", category: .generator, icon: R.Image.Tool.loremIpsumGenerator, sidebarTitle: "tool.ligen.mintitle".localized(), toolDescription: "tool.ligen.description".localized(), viewController: LoremIpsumGeneratorViewController() ) static let checksumGenerator = Tool( title: "tool.checksum.title".localized(), identifier: "checksum.generator", category: .generator, icon: R.Image.Tool.hashGenerator, sidebarTitle: "tool.checksum.mintitle".localized(), toolDescription: "tool.checksum.description".localized(), viewController: ChecksumGeneratorViewController() ) static let qrGenerator = Tool( title: "QR Code Generator".localized(), identifier: "qrgenerator", category: .generator, icon: R.Image.Tool.qrgenerator, sidebarTitle: "QR Code Generator".localized(), toolDescription: "Create a QR code from text".localized(), viewController: QRCodeGeneratorViewController() ) // MARK: - Text - static let textInspector = Tool( title: "tool.textinspect.title".localized(), identifier: "textinspect", category: .text, icon: R.Image.Tool.textInspector, sidebarTitle: "tool.textinspect.mintitle".localized(), toolDescription: "tool.textinspect.description".localized(), viewController: TextInspectorViewController() ) static let regexTester = Tool( title: "tool.regex.title".localized(), identifier: "regex", category: .text, icon: R.Image.Tool.regexTester, sidebarTitle: "tool.regex.mintitle".localized(), toolDescription: "tool.regex.description".localized(), viewController: RegexTesterViewController() ) static let textDiff = Tool( title: "Text Diff".localized(), identifier: "textdiff", category: .text, icon: R.Image.Tool.textInspector, sidebarTitle: "Text Diff".localized(), toolDescription: "Compare two Text and display Diff".localized(), viewController: TextDiffViewController() ) static let hyphenationRemover = Tool( title: "tool.hyphenremove.title".localized(), identifier: "hyphenremove", category: .text, icon: R.Image.Tool.textInspector, sidebarTitle: "tool.hyphenremove.mintitle".localized(), toolDescription: "tool.hyphenremove.description".localized(), viewController: HyphenationRemoverViewController() ) static let jsonSearch = Tool( title: "JSON Search".localized(), identifier: "jsonsearch", category: .text, icon: R.Image.Tool.jsonSearch, sidebarTitle: "JSON Search".localized(), toolDescription: "Extract data from JSON in several ways".localized(), viewController: JSONSearchViewController() ) // MARK: - Graphic - static let imageOptimizer = Tool( title: "tool.imageoptim.title".localized(), identifier: "imageoptim", category: .graphic, icon: R.Image.Tool.imageCompressor, sidebarTitle: "tool.imageoptim.mintitle".localized(), toolDescription: "tool.imageoptim.description".localized(), viewController: ImageOptimizerViewController() ) static let pdfGenerator = Tool( title: "tool.pdfgen.title".localized(), identifier: "pdfgen", category: .graphic, icon: R.Image.Tool.pdfGenerator, sidebarTitle: "tool.pdfgen.mintitle".localized(), toolDescription: "tool.pdfgen.description".localized(), viewController: PDFGeneratorViewController() ) static let imageConverter = Tool( title: "tool.imageconvert.title".localized(), identifier: "imageconvert", category: .graphic, icon: R.Image.Tool.imageConverter, sidebarTitle: "tool.imageconvert.mintitle".localized(), toolDescription: "tool.imageconvert.description".localized(), viewController: ImageConverterViewController() ) static let iconGenerator = Tool( title: "Icon Generator".localized(), identifier: "icongenerator", category: .graphic, icon: R.Image.Tool.iconGenerator, sidebarTitle: "Icon Generator".localized(), toolDescription: "Create a Icon from image".localized(), viewController: IconGeneratorViewController() ) static let qrReader = Tool( title: "QR Code Reader".localized(), identifier: "qrreader", category: .graphic, icon: R.Image.Tool.iconGenerator, sidebarTitle: "QR Code Reader".localized(), toolDescription: "Read QR Code from image or device camera".localized(), viewController: QRCodeReaderViewController() ) // MARK: - Media - static let colorPicker = Tool( title: "Color Picker".localized(), identifier: "colorpicker", category: .media, icon: R.Image.Tool.colorPicker, sidebarTitle: "Color Picker".localized(), toolDescription: "Picker the color and copy components".localized(), viewController: ColorPickerViewController() ) static let audioConverter = Tool( title: "Audio Converter".localized(), identifier: "audioconverter", category: .media, icon: R.Image.Tool.audioConverter, sidebarTitle: "Audio Converter".localized(), toolDescription: "Convert audio from one format to another".localized(), viewController: AudioConverterViewController() ) static let gifConverter = Tool( title: "Gif Converter".localized(), identifier: "gifconverter", category: .media, icon: R.Image.Tool.gif, sidebarTitle: "Gif Converter".localized(), toolDescription: "Convert a movie to an animated GIF file".localized(), viewController: GifConverterViewController() ) } extension ToolManager { static let shared = ToolManager() => { toolManager in toolManager.registerTool(.home) toolManager.registerTool(.jsonYamlConverter) toolManager.registerTool(.numberBaseConverter) toolManager.registerTool(.dateConverter) toolManager.registerTool(.htmlCoder) toolManager.registerTool(.urlCoder) toolManager.registerTool(.base64Coder) toolManager.registerTool(.jwtCoder) toolManager.registerTool(.jsonFormatter) toolManager.registerTool(.xmlFormatter) toolManager.registerTool(.sqlFormatter) toolManager.registerTool(.hashGenerator) toolManager.registerTool(.uuidGenerator) toolManager.registerTool(.loremIpsumGenerator) toolManager.registerTool(.checksumGenerator) toolManager.registerTool(.qrGenerator) toolManager.registerTool(.textInspector) toolManager.registerTool(.regexTester) toolManager.registerTool(.textDiff) toolManager.registerTool(.hyphenationRemover) toolManager.registerTool(.imageOptimizer) toolManager.registerTool(.pdfGenerator) toolManager.registerTool(.imageConverter) toolManager.registerTool(.iconGenerator) toolManager.registerTool(.qrReader) toolManager.registerTool(.colorPicker) toolManager.registerTool(.audioConverter) toolManager.registerTool(.gifConverter) toolManager.registerTool(.settings) } } ================================================ FILE: DevToys/DevToys/Model/ToolCategory+Default.swift ================================================ // // ToolCategory+Default.swift // DevToys // // Created by yuki on 2022/02/13. // extension ToolCategory { static let home = ToolCategory(name: "category.home".localized(), icon: nil, identifier: "home", shouldHideCategory: true) static let converter = ToolCategory(name: "category.converters".localized(), icon: R.Image.Tool.convert, identifier: "converters") static let encoderDecoder = ToolCategory(name: "category.encoders_decoders".localized(), icon: R.Image.Tool.encoderDecoder, identifier: "encoderDecoder") static let formatter = ToolCategory(name: "category.formatters".localized(), icon: R.Image.Tool.formatter, identifier: "formatter") static let generator = ToolCategory(name: "category.generators".localized(), icon: R.Image.Tool.generator, identifier: "generator") static let text = ToolCategory(name: "category.text".localized(), icon: R.Image.Tool.text, identifier: "text") static let graphic = ToolCategory(name: "category.graphic".localized(), icon: R.Image.Tool.graphic, identifier: "graphic") static let media = ToolCategory(name: "Media".localized(), icon: R.Image.Tool.media, identifier: "media") static let settings = ToolCategory(name: "Settings".localized(), icon: nil, identifier: "settings", shouldHideCategory: true) } ================================================ FILE: DevToys/DevToys/Model/ToolManager+.swift ================================================ // // ToolManager.swift // DevToys // // Created by yuki on 2022/02/12. // import CoreUtil import OrderedCollections final class ToolManager { private var toolCategoryMap = [ToolCategory: [Tool]]() private var toolIdentifierMap = [String: Tool]() private var categoryIdentifierMap = [String: ToolCategory]() private var categories = OrderedSet() func registerTool(_ tool: Tool) { self.toolCategoryMap.arrayAppend(tool, forKey: tool.category) if !categories.contains(tool.category) { self.categories.append(tool.category) self.categoryIdentifierMap[tool.category.identifier] = tool.category } assert(toolIdentifierMap[tool.identifier] == nil, "Tool with identifier '\(tool.identifier)' has already been registered.") self.toolIdentifierMap[tool.identifier] = tool } func allTools() -> [Tool] { self.categories.compactMap{ toolCategoryMap[$0] }.reduce(into: [], +=) } func toolForIdentifier(_ identifier: String) -> Tool? { self.toolIdentifierMap[identifier] } func categoryForIdentifier(_ identifier: String) -> ToolCategory? { self.categoryIdentifierMap[identifier] } func toolsForCategory(_ category: ToolCategory) -> [Tool] { self.toolCategoryMap[category] ?? [] } func flattenRootItems() -> [Any] { var items = [Any]() for category in categories { if category.shouldHideCategory { items.append(contentsOf: toolsForCategory(category)) } else if !toolsForCategory(category).isEmpty { items.append(category) } } return items } } final class ToolCategory: Hashable { let name: String let icon: NSImage? let identifier: String let shouldHideCategory: Bool static func == (lhs: ToolCategory, rhs: ToolCategory) -> Bool { lhs.identifier == rhs.identifier } func hash(into hasher: inout Hasher) { hasher.combine(identifier) } init(name: String, icon: NSImage?, identifier: String, shouldHideCategory: Bool = false) { self.name = name self.icon = icon self.identifier = identifier self.shouldHideCategory = shouldHideCategory } } final class Tool { let title: String let identifier: String let category: ToolCategory let icon: NSImage let sidebarTitle: String let toolDescription: String let showAlways: Bool let showOnHome: Bool let showOnSidebar: Bool private(set) lazy var viewController = makeViewController() private let makeViewController: () -> NSViewController init(title: String, identifier: String, category: ToolCategory, icon: NSImage, sidebarTitle: String? = nil, toolDescription: String, showAlways: Bool = false, showOnHome: Bool = true, showOnSidebar: Bool = true, viewController: @autoclosure @escaping () -> NSViewController) { self.title = title self.identifier = identifier self.category = category self.icon = icon self.sidebarTitle = sidebarTitle ?? title self.toolDescription = toolDescription self.showAlways = showAlways self.showOnHome = showOnHome self.showOnSidebar = showOnSidebar self.makeViewController = viewController } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "filename" : "icon_16x16.png", "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { "filename" : "icon_16x16@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { "filename" : "icon_32x32.png", "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { "filename" : "icon_32x32@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { "filename" : "icon_128x128.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { "filename" : "icon_128x128@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { "filename" : "icon_256x256.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { "filename" : "icon_256x256@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { "filename" : "icon_512x512.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { "filename" : "icon_512x512@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/applewatch.imageset/Contents.json ================================================ { "images" : [ { "filename" : "applewatch.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/carplay.imageset/Contents.json ================================================ { "images" : [ { "filename" : "carplay.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/check.imageset/Contents.json ================================================ { "images" : [ { "filename" : "Artboard Copy 22.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/clear.imageset/Contents.json ================================================ { "images" : [ { "filename" : "clear.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/convert.imageset/Contents.json ================================================ { "images" : [ { "filename" : "convert.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/copy.imageset/Contents.json ================================================ { "images" : [ { "filename" : "copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/drop.imageset/Contents.json ================================================ { "images" : [ { "filename" : "drop.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/error.imageset/Contents.json ================================================ { "images" : [ { "filename" : "Artboard Copy 23.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/export.imageset/Contents.json ================================================ { "images" : [ { "filename" : "export.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/format.imageset/Contents.json ================================================ { "images" : [ { "filename" : "format.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/hyphen.imageset/Contents.json ================================================ { "images" : [ { "filename" : "hyphen.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/ipad.imageset/Contents.json ================================================ { "images" : [ { "filename" : "ipad.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/ipaddress.imageset/Contents.json ================================================ { "images" : [ { "filename" : "ipaddress.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/iphone.imageset/Contents.json ================================================ { "images" : [ { "filename" : "iphone.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/mac.imageset/Contents.json ================================================ { "images" : [ { "filename" : "mac.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/network.status.imageset/Contents.json ================================================ { "images" : [ { "filename" : "network.status.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/number.imageset/Contents.json ================================================ { "images" : [ { "filename" : "text copy 5.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/open.imageset/Contents.json ================================================ { "images" : [ { "filename" : "open.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/paramators.imageset/Contents.json ================================================ { "images" : [ { "filename" : "paramators.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/paste.imageset/Contents.json ================================================ { "images" : [ { "filename" : "paste.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/pulldown.indicator.imageset/Contents.json ================================================ { "images" : [ { "filename" : "text copy 26.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/search.imageset/Contents.json ================================================ { "images" : [ { "filename" : "text copy 25.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/settings.imageset/Contents.json ================================================ { "images" : [ { "filename" : "settings.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/sidebar.disclosure.imageset/Contents.json ================================================ { "images" : [ { "filename" : "text copy 23.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/spacing.imageset/Contents.json ================================================ { "images" : [ { "filename" : "number.base.convert copy 2.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/speed.imageset/Contents.json ================================================ { "images" : [ { "filename" : "speed.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/spuit.imageset/Contents.json ================================================ { "images" : [ { "filename" : "spuit.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/stepper.down.imageset/Contents.json ================================================ { "images" : [ { "filename" : "Artboard Copy 2.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/stepper.up.imageset/Contents.json ================================================ { "images" : [ { "filename" : "Artboard.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/text.imageset/Contents.json ================================================ { "images" : [ { "filename" : "text.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "provides-namespace" : true } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/api.imageset/Contents.json ================================================ { "images" : [ { "filename" : "text copy 11.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/audio.convert.imageset/Contents.json ================================================ { "images" : [ { "filename" : "audio.convert.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/base64.coder.imageset/Contents.json ================================================ { "images" : [ { "filename" : "base64.coder copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/color.blindness.simulator.imageset/Contents.json ================================================ { "images" : [ { "filename" : "color.blindness.simulator copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/color.picker.imageset/Contents.json ================================================ { "images" : [ { "filename" : "color.picker.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/convert.imageset/Contents.json ================================================ { "images" : [ { "filename" : "convert copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/date.convert.imageset/Contents.json ================================================ { "images" : [ { "filename" : "text copy 13.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/encoder.decoder.imageset/Contents.json ================================================ { "images" : [ { "filename" : "encoder.decoder copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/formatter.imageset/Contents.json ================================================ { "images" : [ { "filename" : "formatter copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/generator.imageset/Contents.json ================================================ { "images" : [ { "filename" : "generator copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/gif.imageset/Contents.json ================================================ { "images" : [ { "filename" : "gif.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/graphic.imageset/Contents.json ================================================ { "images" : [ { "filename" : "graphic copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/hash.generator.imageset/Contents.json ================================================ { "images" : [ { "filename" : "hash.generator copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/home.imageset/Contents.json ================================================ { "images" : [ { "filename" : "home copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/html.coder.imageset/Contents.json ================================================ { "images" : [ { "filename" : "html.coder copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/icon.generator.imageset/Contents.json ================================================ { "images" : [ { "filename" : "icon.generator.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/image.compressor.imageset/Contents.json ================================================ { "images" : [ { "filename" : "image.compressor copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/image.converter.imageset/Contents.json ================================================ { "images" : [ { "filename" : "image.converter.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/json.convert.imageset/Contents.json ================================================ { "images" : [ { "filename" : "json.convert copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/json.formatter.imageset/Contents.json ================================================ { "images" : [ { "filename" : "json.formatter copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/json.search.imageset/Contents.json ================================================ { "images" : [ { "filename" : "json.search.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/jwt.coder.imageset/Contents.json ================================================ { "images" : [ { "filename" : "jwt copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/lorem.ipsum.generator.imageset/Contents.json ================================================ { "images" : [ { "filename" : "lorem.ipsum.generator copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/markdown.preview.imageset/Contents.json ================================================ { "images" : [ { "filename" : "Artboard Copy 21.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/media.imageset/Contents.json ================================================ { "images" : [ { "filename" : "media.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/network.imageset/Contents.json ================================================ { "images" : [ { "filename" : "network.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/number.base.convert.imageset/Contents.json ================================================ { "images" : [ { "filename" : "number.base.convert copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/pdf.imageset/Contents.json ================================================ { "images" : [ { "filename" : "text copy 15.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/qr.generator.imageset/Contents.json ================================================ { "images" : [ { "filename" : "qr.generator.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/regex.tester.imageset/Contents.json ================================================ { "images": [ { "filename": "regex.tester copy.pdf", "idiom": "universal", "scale": "1x" }, { "idiom": "universal", "scale": "2x" }, { "idiom": "universal", "scale": "3x" } ], "info": { "author": "xcode", "version": 1 }, "properties": { "template-rendering-intent": "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/settings.imageset/Contents.json ================================================ { "images" : [ { "filename" : "settings.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/sql.formatter.imageset/Contents.json ================================================ { "images" : [ { "filename" : "sql.formatter.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/text.diff.imageset/Contents.json ================================================ { "images" : [ { "filename" : "text.diff copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/text.imageset/Contents.json ================================================ { "images" : [ { "filename" : "text copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/text.inspector.imageset/Contents.json ================================================ { "images": [ { "filename": "text.inspector copy.pdf", "idiom": "universal", "scale": "1x" }, { "idiom": "universal", "scale": "2x" }, { "idiom": "universal", "scale": "3x" } ], "info": { "author": "xcode", "version": 1 }, "properties": { "template-rendering-intent": "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/url.coder.imageset/Contents.json ================================================ { "images" : [ { "filename" : "url.coder copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/uuid.generator.imageset/Contents.json ================================================ { "images" : [ { "filename" : "uuid.generator copy.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/tool/xml.formatter.imageset/Contents.json ================================================ { "images" : [ { "filename" : "xml.format.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: DevToys/DevToys/Resource/Assets.xcassets/transparent_background.imageset/Contents.json ================================================ { "images" : [ { "filename" : "Group.pdf", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: DevToys/DevToys/Resource/Base.lproj/Main.storyboard ================================================ Default Left to Right Right to Left Default Left to Right Right to Left ================================================ FILE: DevToys/DevToys/Resource/R.swift ================================================ // // R.swift // DevToys // // Created by yuki on 2022/01/29. // import Cocoa enum R { enum Size { static let corner: CGFloat = 5 static let controlTitleFontSize: CGFloat = 12 static let controlFontSize: CGFloat = 10.5 static let codeFontSize: CGFloat = 12 static let controlHeight: CGFloat = 26 } enum Color { static var controlBackgroundColor: NSColor { NSColor.textColor.withAlphaComponent(0.08) } static var controlHighlightedBackgroundColor: NSColor { NSColor.textColor.withAlphaComponent(0.15) } static let transparentBackground = NSColor(patternImage: NSImage(named: "transparent_background")!) } enum Image { static let sidebarDisclosure = NSImage(named: "sidebar.disclosure")! static let pulldownIndicator = NSImage(named: "pulldown.indicator")! static let iphone = NSImage(named: "iphone")! static let ipad = NSImage(named: "ipad")! static let mac = NSImage(named: "mac")! static let carplay = NSImage(named: "carplay")! static let appleWatch = NSImage(named: "applewatch")! static let spuit = NSImage(named: "spuit")! static let search = NSImage(named: "search")! static let check = NSImage(named: "check")! static let error = NSImage(named: "error")! static let spacing = NSImage(named: "spacing")! static let clear = NSImage(named: "clear")! static let open = NSImage(named: "open")! static let paste = NSImage(named: "paste")! static let copy = NSImage(named: "copy")! static let convert = NSImage(named: "convert")! static let format = NSImage(named: "format")! static let hyphen = NSImage(named: "hyphen")! static let paramators = NSImage(named: "paramators")! static let settings = NSImage(named: "settings")! static let number = NSImage(named: "number")! static let text = NSImage(named: "text")! static let stepperUp = NSImage(named: "stepper.up")! static let stepperDown = NSImage(named: "stepper.down")! static let ipaddress = NSImage(named: "ipaddress")! static let networkStatus = NSImage(named: "network.status")! static let speed = NSImage(named: "speed")! static let drop = NSImage(named: "drop")! static let export = NSImage(named: "export")! enum Tool { static let home = NSImage(named: "tool/home")! static let settings = NSImage(named: "tool/settings")! static let convert = NSImage(named: "tool/convert")! static let jsonConvert = NSImage(named: "tool/json.convert")! static let numberBaseConvert = NSImage(named: "tool/number.base.convert")! static let dateConvert = NSImage(named: "tool/date.convert")! static let encoderDecoder = NSImage(named: "tool/encoder.decoder")! static let htmlCoder = NSImage(named: "tool/html.coder")! static let urlCoder = NSImage(named: "tool/url.coder")! static let base64Coder = NSImage(named: "tool/base64.coder")! static let jwtCoder = NSImage(named: "tool/jwt.coder")! static let formatter = NSImage(named: "tool/formatter")! static let jsonFormatter = NSImage(named: "tool/json.formatter")! static let xmlFormatter = NSImage(named: "tool/xml.formatter")! static let sqlFormatter = NSImage(named: "tool/sql.formatter")! static let generator = NSImage(named: "tool/generator")! static let uuidGenerator = NSImage(named: "tool/uuid.generator")! static let hashGenerator = NSImage(named: "tool/hash.generator")! static let loremIpsumGenerator = NSImage(named: "tool/lorem.ipsum.generator")! static let text = NSImage(named: "tool/text")! static let textInspector = NSImage(named: "tool/text.inspector")! static let regexTester = NSImage(named: "tool/regex.tester")! static let textDiff = NSImage(named: "tool/text.diff")! static let markdownPreview = NSImage(named: "tool/markdown.preview")! static let jsonSearch = NSImage(named: "tool/json.search")! static let graphic = NSImage(named: "tool/graphic")! static let pdfGenerator = NSImage(named: "tool/pdf")! static let colorBlindnessSimulator = NSImage(named: "tool/color.blindness.simulator")! static let imageCompressor = NSImage(named: "tool/image.compressor")! static let imageConverter = NSImage(named: "tool/image.converter")! static let colorPicker = NSImage(named: "tool/color.picker")! static let gif = NSImage(named: "tool/gif")! static let qrgenerator = NSImage(named: "tool/qr.generator")! static let iconGenerator = NSImage(named: "tool/icon.generator")! static let media = NSImage(named: "tool/media")! static let audioConverter = NSImage(named: "tool/audio.convert")! static let network = NSImage(named: "tool/network")! static let api = NSImage(named: "tool/api")! } } } extension Bundle { static let current = Bundle(for: { class __ {}; return __.self }()) } ================================================ FILE: DevToys/DevToys/Resource/de.lproj/Localizable.strings ================================================ /* Localizable.strings DevToys Created by mheob on 2022/02/14. */ // - General - "Configuration" = "Konfiguration"; "Format" = "Format"; "Pretty" = "Pretty"; "Minified" = "Minified"; "Encoded" = "Encoded"; "Decoded" = "Decoded"; "File" = "Datei"; "Text" = "Text"; "Copy" = "Kopieren"; "Paste" = "Einfügen"; "Copied!" = "Kopiert!"; "Pasted!" = "Eingefügt"; "Open" = "Open"; "Export" = "Exportieren"; "Import" = "Importieren"; "Input" = "Eingabe"; "Output" = "Ausgabe"; "No selection" = "Keine Auswahl"; "Drop Files Here" = "Dateien hier ablegen"; "Untitled Tool" = "Unbenanntes Werkzeug"; "Uppercase" = "Großbuchstaben"; "Hyphens" = "Silbentrennungen"; "Type" = "Typ"; "Length" = "Länge"; "Convert" = "Konvertieren"; "Information" = "Information"; "Clear" = "Leeren"; "Delete" = "Löschen"; // - Category - "category.home" = "Home"; "category.converters" = "Konverter"; "category.encoders_decoders" = "Encoders / Decoders"; "category.formatters" = "Formatierer"; "category.generators" = "Generatoren"; "category.text" = "Text"; "category.graphic" = "Grafik"; // - Tool - "tool.home.title" = "Home"; "tool.home.mintitle" = "Home"; "tool.home.description" = "Suche alle Tools."; "tool.jsonyaml.title" = "JSON <> Yaml Konverter"; "tool.jsonyaml.mintitle" = "JSON <> Yaml"; "tool.jsonyaml.description" = "Json-Daten in Yaml konvertieren und umgekehrt"; "tool.numbase.title" = "Zahlenbasis-Konverter"; "tool.numbase.mintitle" = "Zahlenbasis"; "tool.numbase.description" = "Zahlen von einer Basis in eine andere umrechnen"; "Format Number" = "Zahlenformat"; "Decimal" = "Dezimal"; "Hexdecimal" = "Hexadezimal"; "Octal" = "Oktal"; "Binary" = "Binär"; "tool.date.title" = "Datumskonverter"; "tool.date.mintitle" = "Datum"; "tool.date.description" = "Konvertiert das Datum von einem Format in ein anderes"; "Now" = "Jetzt"; "Date" = "Datum"; "Unix Time" = "Unix Time"; "ISO 8601" = "ISO 8601"; "Calender" = "Kalender"; "tool.html.title" = "HTML Encoder / Decoder"; "tool.html.mintitle" = "HTML"; "tool.html.description" = "Kodiert oder dekodiert alle zutreffenden Zeichen in ihr entsprechendes HTML"; "tool.url.title" = "URL Encoder / Decoder"; "tool.url.mintitle" = "URL"; "tool.url.description" = "Kodiert oder dekodiert alle zutreffenden Zeichen zu ihrer entsprechenden URL"; "tool.base64.title" = "Base64 Encoder / Decoder"; "tool.base64.mintitle" = "Base64"; "tool.base64.description" = "Base64-Daten kodieren und dekodieren"; "Source Type" = "Quellenart"; "Text Source" = "Textquelle"; "File Source" = "Dateiquelle"; "tool.jwt.title" = "JWT Encoder / Decoder"; "tool.jwt.mintitle" = "JWT"; "tool.jwt.description" = "Dekodiere einen JWT-Header-Payload und eine Signatur"; "JWT Token" = "JWT Token"; "Header" = "Header"; "Payload" = "Payload"; "tool.jsonformat.title" = "JSON Formatter"; "tool.jsonformat.mintitle" = "JSON"; "tool.jsonformat.description" = "Json-Daten einrücken oder verkleinern"; "Indentation" = "Einrückung"; "2 Spaces" = "2 Leerzeichen"; "4 Spaces" = "4 Leerzeichen"; "1 Tab" = "1 Tab"; "tool.xmlformat.title" = "XML Formatter"; "tool.xmlformat.mintitle" = "XML"; "tool.xmlformat.description" = "XML-Daten einrücken oder verkleinern"; "HTML Document" = "HTML Dokument"; "XML Document" = "XML Dokument"; "Document Type" = "Dokumentenart"; "Auto Fix Document" = "Auto Fix Dokument"; "Pretty Document" = "Pretty Dokument"; "tool.hashgen.title" = "Hash Generator"; "tool.hashgen.mintitle" = "Hash"; "tool.hashgen.description" = "Berechne MD5, SHA1, SHA256 und SHA512 Hashes aus Textdaten"; "tool.uuidgen.title" = "UUID Generator"; "tool.uuidgen.mintitle" = "UUID"; "tool.uuidgen.description" = "UUIDs generieren"; "Generate UUIDs" = "UUIDs generieren"; "tool.ligen.title" = "Lorem Ipsum Generator"; "tool.ligen.mintitle" = "Lorem Ipsum"; "tool.ligen.description" = "Lorem Ipsum Platzhaltertext generieren"; "Type of generating Lorem Ipsum" = "Art der Lorem Ipsum Erzeugung"; "Length of generating Lorem Ipsum" = "Länge der Lorem Ipsum Erzeugung"; "Words" = "Wörter"; "Sentences" = "Sätze"; "Paragraphs" = "Paragraphen"; "tool.checksum.title" = "Prüfsummen Generator"; "tool.checksum.mintitle" = "Prüfsumme"; "tool.checksum.description" = "Prüfsumme einer Datei generieren oder testen"; "Output Comparer" = "Ausgangsvergleicher"; "Hash Algorithm" = "Hash-Algorithmus"; "Select which algorithm you want to use" = "Wähle aus, welchen Algorithmus du verwenden möchtest"; "tool.checksum.title" = "Prüfsummen Generator"; "tool.checksum.mintitle" = "Prüfsumme"; "tool.checksum.description" = "Prüfsumme einer Datei generieren oder testen"; "tool.textinspect.title" = "Text Inspektor & Groß-/Kleinschreibung Konverter"; "tool.textinspect.mintitle" = "Inspektor & Fallumwandler"; "tool.textinspect.description" = "Text analysieren und in verschiedene Groß- und Kleinschreibung umwandeln"; "Charactors" = "Schriftzeichen"; "Words" = "Wörter"; "Lines" = "Lines"; "Bytes" = "Bytes"; "tool.regex.title" = "Regex Tester"; "tool.regex.mintitle" = "Regex"; "tool.regex.description" = "Regex testen"; "Reguler expression" = "Regex Ausdruck"; "tool.hyphenremove.title" = "Silbentrennung-Entferner"; "tool.hyphenremove.mintitle" = "Silbentrennung"; "tool.hyphenremove.description" = "Aus PDF-Text kopierte Silbentrennungen entfernen"; "tool.imageoptim.title" = "PNG / JPEG Kompressor"; "tool.imageoptim.mintitle" = "PNG / JPEG Kompressor"; "tool.imageoptim.description" = "Verlustfreier PNG- und JPEG-Optimierer"; "Optimize Level" = "Optimierungslevel"; "Low" = "Niedrig"; "Medium" = "Mittel"; "High" = "Hoch"; "Very High (Slow)" = "Sehr hoch (langsam)"; "tool.pdfgen.title" = "PDF Generator"; "tool.pdfgen.mintitle" = "PDF Generator"; "tool.pdfgen.description" = "PDF aus mehreren Bildern generieren"; "Images" = "Bilder"; "Size" = "Größe"; "Generate PDF" = "PDF generieren"; "Page" = "Seite"; "tool.imageconvert.title" = "Bildkonverter"; "tool.imageconvert.mintitle" = "Bildkonverter"; "tool.imageconvert.description" = "Bilder konvertieren oder Größe ändern"; "PNG Format" = "PNG Format"; "JPEG Format" = "JPEG Format"; "TIFF Format" = "TIFF Format"; "GIF Format" = "GIF Format"; "Scale to Fill" = "Skalieren zum Füllen"; "Scale to Fit" = "Skalieren und einpassen"; "Image Format" = "Bildformat"; "Resize" = "Größe ändern"; "Converted Images" = "Umgewandelte Bilder"; "Scale" = "Skalierung"; "Size" = "Größe"; ================================================ FILE: DevToys/DevToys/Resource/de.lproj/Main.strings ================================================ /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */ "1UK-8n-QPP.title" = "Customize Toolbar…"; /* Class = "NSMenuItem"; title = "DevToys"; ObjectID = "1Xt-HY-uBw"; */ "1Xt-HY-uBw.title" = "DevToys"; /* Class = "NSMenu"; title = "Find"; ObjectID = "1b7-l0-nxx"; */ "1b7-l0-nxx.title" = "Find"; /* Class = "NSMenuItem"; title = "Lower"; ObjectID = "1tx-W0-xDw"; */ "1tx-W0-xDw.title" = "Lower"; /* Class = "NSMenuItem"; title = "Raise"; ObjectID = "2h7-ER-AoG"; */ "2h7-ER-AoG.title" = "Raise"; /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */ "2oI-Rn-ZJC.title" = "Transformations"; /* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */ "3IN-sU-3Bg.title" = "Spelling"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "3Om-Ey-2VK"; */ "3Om-Ey-2VK.title" = "Use Default"; /* Class = "NSMenu"; title = "Speech"; ObjectID = "3rS-ZA-NoH"; */ "3rS-ZA-NoH.title" = "Speech"; /* Class = "NSMenuItem"; title = "Tighten"; ObjectID = "46P-cB-AYj"; */ "46P-cB-AYj.title" = "Tighten"; /* Class = "NSMenuItem"; title = "Find"; ObjectID = "4EN-yA-p0u"; */ "4EN-yA-p0u.title" = "Find"; /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */ "4J7-dP-txa.title" = "Enter Full Screen"; /* Class = "NSMenuItem"; title = "Quit DevToys"; ObjectID = "4sb-4s-VLi"; */ "4sb-4s-VLi.title" = "Quit DevToys"; /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ "5QF-Oa-p0T.title" = "Edit"; /* Class = "NSMenuItem"; title = "Copy Style"; ObjectID = "5Vv-lz-BsD"; */ "5Vv-lz-BsD.title" = "Copy Style"; /* Class = "NSMenuItem"; title = "About DevToys"; ObjectID = "5kV-Vb-QxS"; */ "5kV-Vb-QxS.title" = "About DevToys"; /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ "6dh-zS-Vam.title" = "Redo"; /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */ "78Y-hA-62v.title" = "Correct Spelling Automatically"; /* Class = "NSMenu"; title = "Writing Direction"; ObjectID = "8mr-sm-Yjd"; */ "8mr-sm-Yjd.title" = "Writing Direction"; /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */ "9ic-FL-obx.title" = "Substitutions"; /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */ "9yt-4B-nSM.title" = "Smart Copy/Paste"; /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ "AYu-sK-qS6.title" = "Main Menu"; /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ "BOF-NM-1cW.title" = "Preferences…"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "BgM-ve-c93"; */ "BgM-ve-c93.title" = "\tLeft to Right"; /* Class = "NSMenuItem"; title = "Save As…"; ObjectID = "Bw7-FT-i3A"; */ "Bw7-FT-i3A.title" = "Save As…"; /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */ "DVo-aG-piG.title" = "Close"; /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */ "Dv1-io-Yv7.title" = "Spelling and Grammar"; /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ "F2S-fz-NVQ.title" = "Help"; /* Class = "NSMenuItem"; title = "DevToys Help"; ObjectID = "FKE-Sm-Kum"; */ "FKE-Sm-Kum.title" = "DevToys Help"; /* Class = "NSMenuItem"; title = "Text"; ObjectID = "Fal-I4-PZk"; */ "Fal-I4-PZk.title" = "Text"; /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */ "FeM-D8-WVr.title" = "Substitutions"; /* Class = "NSMenuItem"; title = "Bold"; ObjectID = "GB9-OM-e27"; */ "GB9-OM-e27.title" = "Bold"; /* Class = "NSMenu"; title = "Format"; ObjectID = "GEO-Iw-cKr"; */ "GEO-Iw-cKr.title" = "Format"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "GUa-eO-cwY"; */ "GUa-eO-cwY.title" = "Use Default"; /* Class = "NSMenuItem"; title = "Font"; ObjectID = "Gi5-1S-RQB"; */ "Gi5-1S-RQB.title" = "Font"; /* Class = "NSMenuItem"; title = "Writing Direction"; ObjectID = "H1b-Si-o9J"; */ "H1b-Si-o9J.title" = "Writing Direction"; /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ "H8h-7b-M4v.title" = "View"; /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */ "HFQ-gK-NFA.title" = "Text Replacement"; /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */ "HFo-cy-zxI.title" = "Show Spelling and Grammar"; /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ "HyV-fh-RgO.title" = "View"; /* Class = "NSMenuItem"; title = "Subscript"; ObjectID = "I0S-gh-46l"; */ "I0S-gh-46l.title" = "Subscript"; /* Class = "NSMenuItem"; title = "Open…"; ObjectID = "IAo-SY-fd9"; */ "IAo-SY-fd9.title" = "Open…"; /* Class = "NSWindow"; title = "DevToys"; ObjectID = "IQv-IB-iLA"; */ "IQv-IB-iLA.title" = "DevToys"; /* Class = "NSMenuItem"; title = "Justify"; ObjectID = "J5U-5w-g23"; */ "J5U-5w-g23.title" = "Justify"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "J7y-lM-qPV"; */ "J7y-lM-qPV.title" = "Use None"; /* Class = "NSMenuItem"; title = "Revert to Saved"; ObjectID = "KaW-ft-85H"; */ "KaW-ft-85H.title" = "Revert to Saved"; /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ "Kd2-mp-pUS.title" = "Show All"; /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ "LE2-aR-0XJ.title" = "Bring All to Front"; /* Class = "NSMenuItem"; title = "Paste Ruler"; ObjectID = "LVM-kO-fVI"; */ "LVM-kO-fVI.title" = "Paste Ruler"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "Lbh-J2-qVU"; */ "Lbh-J2-qVU.title" = "\tLeft to Right"; /* Class = "NSMenuItem"; title = "Copy Ruler"; ObjectID = "MkV-Pr-PK5"; */ "MkV-Pr-PK5.title" = "Copy Ruler"; /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ "NMo-om-nkz.title" = "Services"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "Nop-cj-93Q"; */ "Nop-cj-93Q.title" = "\tDefault"; /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ "OY7-WF-poV.title" = "Minimize"; /* Class = "NSMenuItem"; title = "Baseline"; ObjectID = "OaQ-X3-Vso"; */ "OaQ-X3-Vso.title" = "Baseline"; /* Class = "NSMenuItem"; title = "Hide DevToys"; ObjectID = "Olw-nP-bQN"; */ "Olw-nP-bQN.title" = "Hide DevToys"; /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "OwM-mh-QMV"; */ "OwM-mh-QMV.title" = "Find Previous"; /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "Oyz-dy-DGm"; */ "Oyz-dy-DGm.title" = "Stop Speaking"; /* Class = "NSMenuItem"; title = "Bigger"; ObjectID = "Ptp-SP-VEL"; */ "Ptp-SP-VEL.title" = "Bigger"; /* Class = "NSMenuItem"; title = "Show Fonts"; ObjectID = "Q5e-8K-NDq"; */ "Q5e-8K-NDq.title" = "Show Fonts"; /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ "R4o-n2-Eq4.title" = "Zoom"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "RB4-Sm-HuC"; */ "RB4-Sm-HuC.title" = "\tRight to Left"; /* Class = "NSMenuItem"; title = "Superscript"; ObjectID = "Rqc-34-cIF"; */ "Rqc-34-cIF.title" = "Superscript"; /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ "Ruw-6m-B2m.title" = "Select All"; /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "S0p-oC-mLd"; */ "S0p-oC-mLd.title" = "Jump to Selection"; /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ "Td7-aD-5lo.title" = "Window"; /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */ "UEZ-Bs-lqG.title" = "Capitalize"; /* Class = "NSMenuItem"; title = "Center"; ObjectID = "VIY-Ag-zcb"; */ "VIY-Ag-zcb.title" = "Center"; /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ "Vdr-fp-XzO.title" = "Hide Others"; /* Class = "NSMenuItem"; title = "Italic"; ObjectID = "Vjx-xi-njq"; */ "Vjx-xi-njq.title" = "Italic"; /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ "W48-6f-4Dl.title" = "Edit"; /* Class = "NSMenuItem"; title = "Underline"; ObjectID = "WRG-CD-K1S"; */ "WRG-CD-K1S.title" = "Underline"; /* Class = "NSMenuItem"; title = "New"; ObjectID = "Was-JA-tGl"; */ "Was-JA-tGl.title" = "New"; /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ "WeT-3V-zwk.title" = "Paste and Match Style"; /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "Xz5-n4-O0W"; */ "Xz5-n4-O0W.title" = "Find…"; /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "YEy-JH-Tfz"; */ "YEy-JH-Tfz.title" = "Find and Replace…"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "YGs-j5-SAR"; */ "YGs-j5-SAR.title" = "\tDefault"; /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "Ynk-f8-cLZ"; */ "Ynk-f8-cLZ.title" = "Start Speaking"; /* Class = "NSMenuItem"; title = "Align Left"; ObjectID = "ZM1-6Q-yy1"; */ "ZM1-6Q-yy1.title" = "Align Left"; /* Class = "NSMenuItem"; title = "Paragraph"; ObjectID = "ZvO-Gk-QUH"; */ "ZvO-Gk-QUH.title" = "Paragraph"; /* Class = "NSMenuItem"; title = "Print…"; ObjectID = "aTl-1u-JFS"; */ "aTl-1u-JFS.title" = "Print…"; /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ "aUF-d1-5bR.title" = "Window"; /* Class = "NSMenu"; title = "Font"; ObjectID = "aXa-aM-Jaq"; */ "aXa-aM-Jaq.title" = "Font"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "agt-UL-0e3"; */ "agt-UL-0e3.title" = "Use Default"; /* Class = "NSMenuItem"; title = "Show Colors"; ObjectID = "bgn-CT-cEk"; */ "bgn-CT-cEk.title" = "Show Colors"; /* Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu"; */ "bib-Uj-vzu.title" = "File"; /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "buJ-ug-pKt"; */ "buJ-ug-pKt.title" = "Use Selection for Find"; /* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */ "c8a-y6-VQd.title" = "Transformations"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "cDB-IK-hbR"; */ "cDB-IK-hbR.title" = "Use None"; /* Class = "NSMenuItem"; title = "Selection"; ObjectID = "cqv-fj-IhA"; */ "cqv-fj-IhA.title" = "Selection"; /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */ "cwL-P1-jid.title" = "Smart Links"; /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */ "d9M-CD-aMd.title" = "Make Lower Case"; /* Class = "NSMenu"; title = "Text"; ObjectID = "d9c-me-L2H"; */ "d9c-me-L2H.title" = "Text"; /* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */ "dMs-cI-mzQ.title" = "File"; /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ "dRJ-4n-Yzg.title" = "Undo"; /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ "gVA-U4-sdL.title" = "Paste"; /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */ "hQb-2v-fYv.title" = "Smart Quotes"; /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */ "hz2-CU-CR7.title" = "Check Document Now"; /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ "hz9-B4-Xy5.title" = "Services"; /* Class = "NSMenuItem"; title = "Smaller"; ObjectID = "i1d-Er-qST"; */ "i1d-Er-qST.title" = "Smaller"; /* Class = "NSMenu"; title = "Baseline"; ObjectID = "ijk-EB-dga"; */ "ijk-EB-dga.title" = "Baseline"; /* Class = "NSMenuItem"; title = "Kern"; ObjectID = "jBQ-r6-VK2"; */ "jBQ-r6-VK2.title" = "Kern"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "jFq-tB-4Kx"; */ "jFq-tB-4Kx.title" = "\tRight to Left"; /* Class = "NSMenuItem"; title = "Format"; ObjectID = "jxT-CU-nIS"; */ "jxT-CU-nIS.title" = "Format"; /* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */ "kIP-vf-haE.title" = "Show Sidebar"; /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */ "mK6-2p-4JG.title" = "Check Grammar With Spelling"; /* Class = "NSMenuItem"; title = "Ligatures"; ObjectID = "o6e-r0-MWq"; */ "o6e-r0-MWq.title" = "Ligatures"; /* Class = "NSMenu"; title = "Open Recent"; ObjectID = "oas-Oc-fiZ"; */ "oas-Oc-fiZ.title" = "Open Recent"; /* Class = "NSMenuItem"; title = "Loosen"; ObjectID = "ogc-rX-tC1"; */ "ogc-rX-tC1.title" = "Loosen"; /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ "pa3-QI-u2k.title" = "Delete"; /* Class = "NSMenuItem"; title = "Save…"; ObjectID = "pxx-59-PXV"; */ "pxx-59-PXV.title" = "Save…"; /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "q09-fT-Sye"; */ "q09-fT-Sye.title" = "Find Next"; /* Class = "NSMenuItem"; title = "Page Setup…"; ObjectID = "qIS-W8-SiK"; */ "qIS-W8-SiK.title" = "Page Setup…"; /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */ "rbD-Rh-wIN.title" = "Check Spelling While Typing"; /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */ "rgM-f4-ycn.title" = "Smart Dashes"; /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */ "snW-S8-Cw5.title" = "Show Toolbar"; /* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */ "tRr-pd-1PS.title" = "Data Detectors"; /* Class = "NSMenuItem"; title = "Open Recent"; ObjectID = "tXI-mr-wws"; */ "tXI-mr-wws.title" = "Open Recent"; /* Class = "NSMenu"; title = "Kern"; ObjectID = "tlD-Oa-oAM"; */ "tlD-Oa-oAM.title" = "Kern"; /* Class = "NSMenu"; title = "DevToys"; ObjectID = "uQy-DD-JDr"; */ "uQy-DD-JDr.title" = "DevToys"; /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ "uRl-iY-unG.title" = "Cut"; /* Class = "NSMenuItem"; title = "Paste Style"; ObjectID = "vKC-jM-MkH"; */ "vKC-jM-MkH.title" = "Paste Style"; /* Class = "NSMenuItem"; title = "Show Ruler"; ObjectID = "vLm-3I-IUL"; */ "vLm-3I-IUL.title" = "Show Ruler"; /* Class = "NSMenuItem"; title = "Clear Menu"; ObjectID = "vNY-rz-j42"; */ "vNY-rz-j42.title" = "Clear Menu"; /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */ "vmV-6d-7jI.title" = "Make Upper Case"; /* Class = "NSMenu"; title = "Ligatures"; ObjectID = "w0m-vy-SC9"; */ "w0m-vy-SC9.title" = "Ligatures"; /* Class = "NSMenuItem"; title = "Align Right"; ObjectID = "wb2-vD-lq4"; */ "wb2-vD-lq4.title" = "Align Right"; /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ "wpr-3q-Mcd.title" = "Help"; /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ "x3v-GG-iWU.title" = "Copy"; /* Class = "NSMenuItem"; title = "Use All"; ObjectID = "xQD-1f-W4t"; */ "xQD-1f-W4t.title" = "Use All"; /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "xrE-MZ-jX0"; */ "xrE-MZ-jX0.title" = "Speech"; /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */ "z6F-FW-3nz.title" = "Show Substitutions"; ================================================ FILE: DevToys/DevToys/Resource/en.lproj/Localizable.strings ================================================ /* Localizable.strings DevToys Created by yuki on 2022/02/13. */ // - General - "Configuration" = "Configuration"; "Format" = "Format"; "Pretty" = "Pretty"; "Minified" = "Minified"; "Encoded" = "Encoded"; "Decoded" = "Decoded"; "File" = "File"; "Text" = "Text"; "Copy" = "Copy"; "Paste" = "Paste"; "Copied!" = "Copied!"; "Pasted!" = "Pasted"; "Open" = "Open"; "Export" = "Export"; "Import" = "Import"; "Input" = "Input"; "Output" = "Output"; "No selection" = "No selection"; "Drop Files Here" = "Drop Files Here"; "Untitled Tool" = "Untitled Tool"; "Uppercase" = "Uppercase"; "Hyphens" = "Hyphens"; "Type" = "Type"; "Length" = "Length"; "Convert" = "Convert"; "Information" = "Information"; "Clear" = "Clear"; "Delete" = "Delete"; "Open in Finder" = "Open in Finder"; "Quick Look" = "Quick Look"; "Default" = "Default"; // - Category - "category.home" = "Home"; "category.converters" = "Converters"; "category.encoders_decoders" = "Encoders / Decoders"; "category.formatters" = "Formatters"; "category.generators" = "Generators"; "category.text" = "Text"; "category.graphic" = "Graphic"; "Media" = "Media"; // - Tool - "tool.home.title" = "Home"; "tool.home.mintitle" = "Home"; "tool.home.description" = "Search all tools here."; "tool.jsonyaml.title" = "JSON <> Yaml Converter"; "tool.jsonyaml.mintitle" = "JSON <> Yaml"; "tool.jsonyaml.description" = "Convert Json data to Yaml and vice versa"; "tool.numbase.title" = "Number Base Converter"; "tool.numbase.mintitle" = "Number Base"; "tool.numbase.description" = "Convert numbers from one base to another"; "Format Number" = "Format Number"; "Decimal" = "Decimal"; "Hexdecimal" = "Hexadecimal"; "Octal" = "Octal"; "Binary" = "Binary"; "tool.date.title" = "Date Converter"; "tool.date.mintitle" = "Date"; "tool.date.description" = "Converts date from one format to another"; "Now" = "Now"; "Date" = "Date"; "Unix Time" = "Unix Time"; "ISO 8601" = "ISO 8601"; "Calender" = "Calender"; "tool.html.title" = "HTML Encoder / Decoder"; "tool.html.mintitle" = "HTML"; "tool.html.description" = "Encoder or decoder all the applicable characters to their corresponding HTML"; "tool.url.title" = "URL Encoder / Decoder"; "tool.url.mintitle" = "URL"; "tool.url.description" = "Encoder or decode all the applicable characters to their corresponding URL"; "tool.base64.title" = "Base64 Encoder / Decoder"; "tool.base64.mintitle" = "Base64"; "tool.base64.description" = "Encode and decode Base64 data"; "Source Type" = "Source Type"; "Text Source" = "Text Source"; "File Source" = "File Source"; "tool.jwt.title" = "JWT Encoder / Decoder"; "tool.jwt.mintitle" = "JWT"; "tool.jwt.description" = "Decode a JWT header playload and signature"; "JWT Token" = "JWT Token"; "Header" = "Header"; "Payload" = "Payload"; "tool.jsonformat.title" = "JSON Formatter"; "tool.jsonformat.mintitle" = "JSON"; "tool.jsonformat.description" = "Indent or minify Json data"; "Indentation" = "Indentation"; "2 Spaces" = "2 Spaces"; "4 Spaces" = "4 Spaces"; "1 Tab" = "1 Tab"; "tool.xmlformat.title" = "XML Formatter"; "tool.xmlformat.mintitle" = "XML"; "tool.xmlformat.description" = "Indent or minify XML data"; "HTML Document" = "HTML Document"; "XML Document" = "XML Document"; "Document Type" = "Document Type"; "Auto Fix Document" = "Auto Fix Document"; "Pretty Document" = "Pretty Document"; "tool.hashgen.title" = "Hash Generator"; "tool.hashgen.mintitle" = "Hash"; "tool.hashgen.description" = "Calculate MD5, SHA1, SHA256 and SHA 512 hash from text data"; "tool.uuidgen.title" = "UUID Generator"; "tool.uuidgen.mintitle" = "UUID"; "tool.uuidgen.description" = "Generate UUIDs"; "Generate UUIDs" = "Generate UUIDs"; "tool.ligen.title" = "Lorem Ipsum Generator"; "tool.ligen.mintitle" = "Lorem Ipsum"; "tool.ligen.description" = "Generate Lorem Ipsum placeholder text"; "Type of generating Lorem Ipsum" = "Type of generating Lorem Ipsum"; "Length of generating Lorem Ipsum" = "Length of generating Lorem Ipsum"; "Words" = "Words"; "Sentences" = "Sentences"; "Paragraphs" = "Paragraphs"; "tool.checksum.title" = "Checksum Generator"; "tool.checksum.mintitle" = "Checksum"; "tool.checksum.description" = "Generate or Test checksum of a file"; "Output Comparer" = "Output Comparer"; "Hash Algorithm" = "Hash Algorithm"; "Select which algorithm you want to use" = "Select which algorithm you want to use"; "tool.checksum.title" = "Checksum Generator"; "tool.checksum.mintitle" = "Checksum"; "tool.checksum.description" = "Generate or Test checksum of a file"; "tool.textinspect.title" = "Text Inspector & Case Converter"; "tool.textinspect.mintitle" = "Inspector & Case Converter"; "tool.textinspect.description" = "Analyze text and convert it to different case"; "Charactors" = "Characters"; "Words" = "Words"; "Lines" = "Lines"; "Bytes" = "Bytes"; "tool.regex.title" = "Regex Tester"; "tool.regex.mintitle" = "Regex"; "tool.regex.description" = "Test regular expression"; "Reguler expression" = "Regular expression"; "tool.hyphenremove.title" = "Hyphenation Remover"; "tool.hyphenremove.mintitle" = "Hyphenation"; "tool.hyphenremove.description" = "Remove hyphenations copied from PDF text"; "tool.imageoptim.title" = "PNG / JPEG Compressor"; "tool.imageoptim.mintitle" = "PNG / JPEG Compressor"; "tool.imageoptim.description" = "Lossless PNG and JPEG optimizer"; "Optimize Level" = "Optimize Level"; "Low" = "Low"; "Medium" = "Medium"; "High" = "High"; "Very High (Slow)" = "Very High (Slow)"; "Override original file" = "Override original file"; "tool.pdfgen.title" = "PDF Generator"; "tool.pdfgen.mintitle" = "PDF Generator"; "tool.pdfgen.description" = "Generate PDF from multiple images"; "Images" = "Images"; "Size" = "Size"; "Generate PDF" = "Generate PDF"; "Page" = "Page"; "tool.imageconvert.title" = "Image Converter"; "tool.imageconvert.mintitle" = "Image Converter"; "tool.imageconvert.description" = "Convert or Resize images"; "PNG Format" = "PNG Format"; "JPEG Format" = "JPEG Format"; "TIFF Format" = "TIFF Format"; "GIF Format" = "GIF Format"; "Scale to Fill" = "Scale to Fill"; "Scale to Fit" = "Scale to Fit"; "Image Format" = "Image Format"; "Resize" = "Resize"; "Converted Images" = "Converted Images"; "Scale" = "Scale"; "Size" = "Size"; "QR Code Generator" = "QR Code Generator"; "Create a QR code from text" = "Create a QR code from text"; "Correction Level" = "Correction Level"; "QR Code" = "QR Code"; "Settings" = "Settings"; "Setting of application" = "Setting of application"; "App Theme" = "App Theme"; "Select which app theme to display" = "Select which app theme to display"; "Use system setting" = "Use system setting"; "Light mode" = "Light mode"; "Dark mode" = "Dark mode"; "Color Picker" = "Color Picker"; "Picker the color and copy components" = "Pick a color and copy the formatted color"; "HSB Box" = "HSB Box"; "HSB Circle" = "HSB Circle"; "HSB Circle and Bars" = "HSB Circle and Bars"; "Pick Color" = "Pick Color"; "Picker Type" = "Picker Type"; "Color Hex" = "Color Hex"; "Color Copy" = "Color Copy"; "Color Copy Type" = "Type"; "Gif Converter" = "Gif Converter"; "Convert a movie to an animated GIF file" = "Convert a movie to an animated GIF file"; "Width" = "Width"; "The width of the Gif file" = "The width of the Gif file (height will be determined)"; "FPS" = "FPS"; "FPS of the Gif file to be exported" = "FPS of the Gif file to be exported"; "Remove source file" = "Remove source file"; "Whether to delete the source file after exporting a Gif" = "Whether to delete the source file after exporting a Gif"; "Audio Converter" = "Audio Converter"; "Convert audio from one format to another" = "Convert audio from one format to another"; "Whether to delete the source file after exporting a Audio" = "Whether to delete the source file after exporting a Audio"; "Starting..." = "Starting..."; "Complete" = "Complete"; "Convert Failed" = "Convert Failed"; "Text Diff" = "Text Diff"; "Compare two Text and display Diff" = "Compare two Text and display Diff"; "By Characters" = "Characters"; "By Words" = "Words"; "By Lines" = "Lines"; "Input 1" = "Input 1"; "Input 2" = "Input 2"; "Diff Style" = "Diff Style"; ================================================ FILE: DevToys/DevToys/Resource/en.lproj/Main.strings ================================================ /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */ "1UK-8n-QPP.title" = "Customize Toolbar…"; /* Class = "NSMenuItem"; title = "DevToys"; ObjectID = "1Xt-HY-uBw"; */ "1Xt-HY-uBw.title" = "DevToys"; /* Class = "NSMenu"; title = "Find"; ObjectID = "1b7-l0-nxx"; */ "1b7-l0-nxx.title" = "Find"; /* Class = "NSMenuItem"; title = "Lower"; ObjectID = "1tx-W0-xDw"; */ "1tx-W0-xDw.title" = "Lower"; /* Class = "NSMenuItem"; title = "Raise"; ObjectID = "2h7-ER-AoG"; */ "2h7-ER-AoG.title" = "Raise"; /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */ "2oI-Rn-ZJC.title" = "Transformations"; /* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */ "3IN-sU-3Bg.title" = "Spelling"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "3Om-Ey-2VK"; */ "3Om-Ey-2VK.title" = "Use Default"; /* Class = "NSMenu"; title = "Speech"; ObjectID = "3rS-ZA-NoH"; */ "3rS-ZA-NoH.title" = "Speech"; /* Class = "NSMenuItem"; title = "Tighten"; ObjectID = "46P-cB-AYj"; */ "46P-cB-AYj.title" = "Tighten"; /* Class = "NSMenuItem"; title = "Find"; ObjectID = "4EN-yA-p0u"; */ "4EN-yA-p0u.title" = "Find"; /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */ "4J7-dP-txa.title" = "Enter Full Screen"; /* Class = "NSMenuItem"; title = "Quit DevToys"; ObjectID = "4sb-4s-VLi"; */ "4sb-4s-VLi.title" = "Quit DevToys"; /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ "5QF-Oa-p0T.title" = "Edit"; /* Class = "NSMenuItem"; title = "Copy Style"; ObjectID = "5Vv-lz-BsD"; */ "5Vv-lz-BsD.title" = "Copy Style"; /* Class = "NSMenuItem"; title = "About DevToys"; ObjectID = "5kV-Vb-QxS"; */ "5kV-Vb-QxS.title" = "About DevToys"; /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ "6dh-zS-Vam.title" = "Redo"; /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */ "78Y-hA-62v.title" = "Correct Spelling Automatically"; /* Class = "NSMenu"; title = "Writing Direction"; ObjectID = "8mr-sm-Yjd"; */ "8mr-sm-Yjd.title" = "Writing Direction"; /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */ "9ic-FL-obx.title" = "Substitutions"; /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */ "9yt-4B-nSM.title" = "Smart Copy/Paste"; /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ "AYu-sK-qS6.title" = "Main Menu"; /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ "BOF-NM-1cW.title" = "Preferences…"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "BgM-ve-c93"; */ "BgM-ve-c93.title" = "\tLeft to Right"; /* Class = "NSMenuItem"; title = "Save As…"; ObjectID = "Bw7-FT-i3A"; */ "Bw7-FT-i3A.title" = "Save As…"; /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */ "DVo-aG-piG.title" = "Close"; /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */ "Dv1-io-Yv7.title" = "Spelling and Grammar"; /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ "F2S-fz-NVQ.title" = "Help"; /* Class = "NSMenuItem"; title = "DevToys Help"; ObjectID = "FKE-Sm-Kum"; */ "FKE-Sm-Kum.title" = "DevToys Help"; /* Class = "NSMenuItem"; title = "Text"; ObjectID = "Fal-I4-PZk"; */ "Fal-I4-PZk.title" = "Text"; /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */ "FeM-D8-WVr.title" = "Substitutions"; /* Class = "NSMenuItem"; title = "Bold"; ObjectID = "GB9-OM-e27"; */ "GB9-OM-e27.title" = "Bold"; /* Class = "NSMenu"; title = "Format"; ObjectID = "GEO-Iw-cKr"; */ "GEO-Iw-cKr.title" = "Format"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "GUa-eO-cwY"; */ "GUa-eO-cwY.title" = "Use Default"; /* Class = "NSMenuItem"; title = "Font"; ObjectID = "Gi5-1S-RQB"; */ "Gi5-1S-RQB.title" = "Font"; /* Class = "NSMenuItem"; title = "Writing Direction"; ObjectID = "H1b-Si-o9J"; */ "H1b-Si-o9J.title" = "Writing Direction"; /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ "H8h-7b-M4v.title" = "View"; /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */ "HFQ-gK-NFA.title" = "Text Replacement"; /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */ "HFo-cy-zxI.title" = "Show Spelling and Grammar"; /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ "HyV-fh-RgO.title" = "View"; /* Class = "NSMenuItem"; title = "Subscript"; ObjectID = "I0S-gh-46l"; */ "I0S-gh-46l.title" = "Subscript"; /* Class = "NSMenuItem"; title = "Open…"; ObjectID = "IAo-SY-fd9"; */ "IAo-SY-fd9.title" = "Open…"; /* Class = "NSWindow"; title = "DevToys"; ObjectID = "IQv-IB-iLA"; */ "IQv-IB-iLA.title" = "DevToys"; /* Class = "NSMenuItem"; title = "Justify"; ObjectID = "J5U-5w-g23"; */ "J5U-5w-g23.title" = "Justify"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "J7y-lM-qPV"; */ "J7y-lM-qPV.title" = "Use None"; /* Class = "NSMenuItem"; title = "Revert to Saved"; ObjectID = "KaW-ft-85H"; */ "KaW-ft-85H.title" = "Revert to Saved"; /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ "Kd2-mp-pUS.title" = "Show All"; /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ "LE2-aR-0XJ.title" = "Bring All to Front"; /* Class = "NSMenuItem"; title = "Paste Ruler"; ObjectID = "LVM-kO-fVI"; */ "LVM-kO-fVI.title" = "Paste Ruler"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "Lbh-J2-qVU"; */ "Lbh-J2-qVU.title" = "\tLeft to Right"; /* Class = "NSMenuItem"; title = "Copy Ruler"; ObjectID = "MkV-Pr-PK5"; */ "MkV-Pr-PK5.title" = "Copy Ruler"; /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ "NMo-om-nkz.title" = "Services"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "Nop-cj-93Q"; */ "Nop-cj-93Q.title" = "\tDefault"; /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ "OY7-WF-poV.title" = "Minimize"; /* Class = "NSMenuItem"; title = "Baseline"; ObjectID = "OaQ-X3-Vso"; */ "OaQ-X3-Vso.title" = "Baseline"; /* Class = "NSMenuItem"; title = "Hide DevToys"; ObjectID = "Olw-nP-bQN"; */ "Olw-nP-bQN.title" = "Hide DevToys"; /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "OwM-mh-QMV"; */ "OwM-mh-QMV.title" = "Find Previous"; /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "Oyz-dy-DGm"; */ "Oyz-dy-DGm.title" = "Stop Speaking"; /* Class = "NSMenuItem"; title = "Bigger"; ObjectID = "Ptp-SP-VEL"; */ "Ptp-SP-VEL.title" = "Bigger"; /* Class = "NSMenuItem"; title = "Show Fonts"; ObjectID = "Q5e-8K-NDq"; */ "Q5e-8K-NDq.title" = "Show Fonts"; /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ "R4o-n2-Eq4.title" = "Zoom"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "RB4-Sm-HuC"; */ "RB4-Sm-HuC.title" = "\tRight to Left"; /* Class = "NSMenuItem"; title = "Superscript"; ObjectID = "Rqc-34-cIF"; */ "Rqc-34-cIF.title" = "Superscript"; /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ "Ruw-6m-B2m.title" = "Select All"; /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "S0p-oC-mLd"; */ "S0p-oC-mLd.title" = "Jump to Selection"; /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ "Td7-aD-5lo.title" = "Window"; /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */ "UEZ-Bs-lqG.title" = "Capitalize"; /* Class = "NSMenuItem"; title = "Center"; ObjectID = "VIY-Ag-zcb"; */ "VIY-Ag-zcb.title" = "Center"; /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ "Vdr-fp-XzO.title" = "Hide Others"; /* Class = "NSMenuItem"; title = "Italic"; ObjectID = "Vjx-xi-njq"; */ "Vjx-xi-njq.title" = "Italic"; /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ "W48-6f-4Dl.title" = "Edit"; /* Class = "NSMenuItem"; title = "Underline"; ObjectID = "WRG-CD-K1S"; */ "WRG-CD-K1S.title" = "Underline"; /* Class = "NSMenuItem"; title = "New"; ObjectID = "Was-JA-tGl"; */ "Was-JA-tGl.title" = "New"; /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ "WeT-3V-zwk.title" = "Paste and Match Style"; /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "Xz5-n4-O0W"; */ "Xz5-n4-O0W.title" = "Find…"; /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "YEy-JH-Tfz"; */ "YEy-JH-Tfz.title" = "Find and Replace…"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "YGs-j5-SAR"; */ "YGs-j5-SAR.title" = "\tDefault"; /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "Ynk-f8-cLZ"; */ "Ynk-f8-cLZ.title" = "Start Speaking"; /* Class = "NSMenuItem"; title = "Align Left"; ObjectID = "ZM1-6Q-yy1"; */ "ZM1-6Q-yy1.title" = "Align Left"; /* Class = "NSMenuItem"; title = "Paragraph"; ObjectID = "ZvO-Gk-QUH"; */ "ZvO-Gk-QUH.title" = "Paragraph"; /* Class = "NSMenuItem"; title = "Print…"; ObjectID = "aTl-1u-JFS"; */ "aTl-1u-JFS.title" = "Print…"; /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ "aUF-d1-5bR.title" = "Window"; /* Class = "NSMenu"; title = "Font"; ObjectID = "aXa-aM-Jaq"; */ "aXa-aM-Jaq.title" = "Font"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "agt-UL-0e3"; */ "agt-UL-0e3.title" = "Use Default"; /* Class = "NSMenuItem"; title = "Show Colors"; ObjectID = "bgn-CT-cEk"; */ "bgn-CT-cEk.title" = "Show Colors"; /* Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu"; */ "bib-Uj-vzu.title" = "File"; /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "buJ-ug-pKt"; */ "buJ-ug-pKt.title" = "Use Selection for Find"; /* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */ "c8a-y6-VQd.title" = "Transformations"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "cDB-IK-hbR"; */ "cDB-IK-hbR.title" = "Use None"; /* Class = "NSMenuItem"; title = "Selection"; ObjectID = "cqv-fj-IhA"; */ "cqv-fj-IhA.title" = "Selection"; /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */ "cwL-P1-jid.title" = "Smart Links"; /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */ "d9M-CD-aMd.title" = "Make Lower Case"; /* Class = "NSMenu"; title = "Text"; ObjectID = "d9c-me-L2H"; */ "d9c-me-L2H.title" = "Text"; /* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */ "dMs-cI-mzQ.title" = "File"; /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ "dRJ-4n-Yzg.title" = "Undo"; /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ "gVA-U4-sdL.title" = "Paste"; /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */ "hQb-2v-fYv.title" = "Smart Quotes"; /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */ "hz2-CU-CR7.title" = "Check Document Now"; /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ "hz9-B4-Xy5.title" = "Services"; /* Class = "NSMenuItem"; title = "Smaller"; ObjectID = "i1d-Er-qST"; */ "i1d-Er-qST.title" = "Smaller"; /* Class = "NSMenu"; title = "Baseline"; ObjectID = "ijk-EB-dga"; */ "ijk-EB-dga.title" = "Baseline"; /* Class = "NSMenuItem"; title = "Kern"; ObjectID = "jBQ-r6-VK2"; */ "jBQ-r6-VK2.title" = "Kern"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "jFq-tB-4Kx"; */ "jFq-tB-4Kx.title" = "\tRight to Left"; /* Class = "NSMenuItem"; title = "Format"; ObjectID = "jxT-CU-nIS"; */ "jxT-CU-nIS.title" = "Format"; /* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */ "kIP-vf-haE.title" = "Show Sidebar"; /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */ "mK6-2p-4JG.title" = "Check Grammar With Spelling"; /* Class = "NSMenuItem"; title = "Ligatures"; ObjectID = "o6e-r0-MWq"; */ "o6e-r0-MWq.title" = "Ligatures"; /* Class = "NSMenu"; title = "Open Recent"; ObjectID = "oas-Oc-fiZ"; */ "oas-Oc-fiZ.title" = "Open Recent"; /* Class = "NSMenuItem"; title = "Loosen"; ObjectID = "ogc-rX-tC1"; */ "ogc-rX-tC1.title" = "Loosen"; /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ "pa3-QI-u2k.title" = "Delete"; /* Class = "NSMenuItem"; title = "Save…"; ObjectID = "pxx-59-PXV"; */ "pxx-59-PXV.title" = "Save…"; /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "q09-fT-Sye"; */ "q09-fT-Sye.title" = "Find Next"; /* Class = "NSMenuItem"; title = "Page Setup…"; ObjectID = "qIS-W8-SiK"; */ "qIS-W8-SiK.title" = "Page Setup…"; /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */ "rbD-Rh-wIN.title" = "Check Spelling While Typing"; /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */ "rgM-f4-ycn.title" = "Smart Dashes"; /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */ "snW-S8-Cw5.title" = "Show Toolbar"; /* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */ "tRr-pd-1PS.title" = "Data Detectors"; /* Class = "NSMenuItem"; title = "Open Recent"; ObjectID = "tXI-mr-wws"; */ "tXI-mr-wws.title" = "Open Recent"; /* Class = "NSMenu"; title = "Kern"; ObjectID = "tlD-Oa-oAM"; */ "tlD-Oa-oAM.title" = "Kern"; /* Class = "NSMenu"; title = "DevToys"; ObjectID = "uQy-DD-JDr"; */ "uQy-DD-JDr.title" = "DevToys"; /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ "uRl-iY-unG.title" = "Cut"; /* Class = "NSMenuItem"; title = "Paste Style"; ObjectID = "vKC-jM-MkH"; */ "vKC-jM-MkH.title" = "Paste Style"; /* Class = "NSMenuItem"; title = "Show Ruler"; ObjectID = "vLm-3I-IUL"; */ "vLm-3I-IUL.title" = "Show Ruler"; /* Class = "NSMenuItem"; title = "Clear Menu"; ObjectID = "vNY-rz-j42"; */ "vNY-rz-j42.title" = "Clear Menu"; /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */ "vmV-6d-7jI.title" = "Make Upper Case"; /* Class = "NSMenu"; title = "Ligatures"; ObjectID = "w0m-vy-SC9"; */ "w0m-vy-SC9.title" = "Ligatures"; /* Class = "NSMenuItem"; title = "Align Right"; ObjectID = "wb2-vD-lq4"; */ "wb2-vD-lq4.title" = "Align Right"; /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ "wpr-3q-Mcd.title" = "Help"; /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ "x3v-GG-iWU.title" = "Copy"; /* Class = "NSMenuItem"; title = "Use All"; ObjectID = "xQD-1f-W4t"; */ "xQD-1f-W4t.title" = "Use All"; /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "xrE-MZ-jX0"; */ "xrE-MZ-jX0.title" = "Speech"; /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */ "z6F-FW-3nz.title" = "Show Substitutions"; ================================================ FILE: DevToys/DevToys/Resource/es.lproj/Localizable.strings ================================================ /* Localizable.strings DevToys Created by RodrigoTomeES on 2022/02/13. */ // - General - "Configuration" = "Configuración"; "Format" = "Formatear"; "Pretty" = "Pretty"; "Minified" = "Minificado"; "Encoded" = "Codificado"; "Decoded" = "Decodificado"; "File" = "Archivo"; "Text" = "Texto"; "Copy" = "Copiar"; "Paste" = "Pegar"; "Copied!" = "Copiado!"; "Pasted!" = "Pegado"; "Open" = "Abrir"; "Export" = "Exportar"; "Import" = "Importar"; "Input" = "Entrada"; "Output" = "Salida"; "No selection" = "Sin selección"; "Drop Files Here" = "Suelte los archivos aquí"; "Untitled Tool" = "Herramienta sin título"; "Uppercase" = "Mayúscula"; "Hyphens" = "Guiones"; "Type" = "Tipo"; "Length" = "Longitud"; "Convert" = "Convertir"; "Information" = "Información"; "Clear" = "Limpiar"; "Delete" = "Eliminar"; // - Category - "category.home" = "Todas las herramientas"; "category.converters" = "Conversores"; "category.encoders_decoders" = "Codificadores / Decodificadores"; "category.formatters" = "Formateadores"; "category.generators" = "Generadores"; "category.text" = "Texto"; "category.graphic" = "Gráficos"; // - Tool - "tool.home.title" = "Todas las herramientas"; "tool.home.mintitle" = "Todas las herramientas"; "tool.home.description" = "Busca todas las herramientas aquí."; "tool.jsonyaml.title" = "Conversor JSON <> Yaml"; "tool.jsonyaml.mintitle" = "JSON <> Yaml"; "tool.jsonyaml.description" = "Convertir datos Json a Yaml y viceversa"; "tool.numbase.title" = "Conversor de bases numéricas"; "tool.numbase.mintitle" = "Base numérica"; "tool.numbase.description" = "Convertir números desde una base a otra"; "Format Number" = "Formato del número"; "Decimal" = "Decimal"; "Hexdecimal" = "Hexadecimal"; "Octal" = "Octal"; "Binary" = "Binario"; "tool.date.title" = "Conversor de fechas"; "tool.date.mintitle" = "Fecha"; "tool.date.description" = "Convierte fechas de un formato a otro"; "Now" = "Ahora"; "Date" = "Fecha"; "Unix Time" = "Formato Unix"; "ISO 8601" = "ISO 8601"; "Calender" = "Calendario"; "tool.html.title" = "Codificador / Decodificador de HTML"; "tool.html.mintitle" = "HTML"; "tool.html.description" = "Codifica o decodifica todos los caracteres aplicables a HTML"; "tool.url.title" = "Codificador / Decodificador de URLs"; "tool.url.mintitle" = "URL"; "tool.url.description" = "Codifica o decodifica todos los caracteres aplicables a URLs"; "tool.base64.title" = "Codificador / Decodificador de Base64"; "tool.base64.mintitle" = "Base64"; "tool.base64.description" = "Codifica o decodifica datos en Base64"; "Source Type" = "Tipo de fuente"; "Text Source" = "Tipo texto"; "File Source" = "Tipo fichero"; "tool.jwt.title" = "Codificador / Decodificador de JWT"; "tool.jwt.mintitle" = "JWT"; "tool.jwt.description" = "Decodifica la carga útil y firma de encabezado de JWT"; "JWT Token" = "JWT Token"; "Header" = "Header"; "Payload" = "Payload"; "tool.jsonformat.title" = "Formateador de JSON"; "tool.jsonformat.mintitle" = "JSON"; "tool.jsonformat.description" = "Indenta o minifica JSON"; "Indentation" = "Indentación"; "2 Spaces" = "2 espacios"; "4 Spaces" = "4 espacios"; "1 Tab" = "1 tab"; "tool.xmlformat.title" = "Formateador de XML"; "tool.xmlformat.mintitle" = "XML"; "tool.xmlformat.description" = "Indenta o minifica XML"; "HTML Document" = "Documento HTML"; "XML Document" = "Documento XML"; "Document Type" = "Tipo de documento"; "Auto Fix Document" = "Corrección automática del documento"; "Pretty Document" = "Embellecer documento"; "tool.hashgen.title" = "Generador de Hash"; "tool.hashgen.mintitle" = "Hash"; "tool.hashgen.description" = "Calcular el hash en MD5, SHA1, SHA256 y SHA 512 de datos de texto"; "tool.uuidgen.title" = "Generador de UUID"; "tool.uuidgen.mintitle" = "UUID"; "tool.uuidgen.description" = "Generar UUIDs"; "Generate UUIDs" = "Generar UUIDs"; "tool.ligen.title" = "Generador de Lorem Ipsum"; "tool.ligen.mintitle" = "Lorem Ipsum"; "tool.ligen.description" = "Generar Lorem Ipsum"; "Type of generating Lorem Ipsum" = "Tipo de generación de Lorem Ipsum"; "Length of generating Lorem Ipsum" = "Longitud de la generación de Lorem Ipsum"; "Words" = "Palabras"; "Sentences" = "Frases"; "Paragraphs" = "Párrafos"; "tool.checksum.title" = "Generador de Checksum"; "tool.checksum.mintitle" = "Checksum"; "tool.checksum.description" = "Genera o comprueba el checksum de un fichero"; "Output Comparer" = "Comparar resultado"; "Hash Algorithm" = "Algoritmo de hash"; "Select which algorithm you want to use" = "Selecciona el algoritmo que quieres usar"; "tool.checksum.title" = "Generador de Checksum"; "tool.checksum.mintitle" = "Checksum"; "tool.checksum.description" = "Genera o comprueba el checksum de un fichero"; "tool.textinspect.title" = "Inspector y conversor de texto"; "tool.textinspect.mintitle" = "Inspector y conversor de mayúsculas"; "tool.textinspect.description" = "Analiza el texto y convierto a distintos formatos de texto"; "Charactors" = "Caracteres"; "Words" = "Palabras"; "Lines" = "Líneas"; "Bytes" = "Bytes"; "tool.regex.title" = "Evaluador de expresiones regulares"; "tool.regex.mintitle" = "Evaluador de Regex"; "tool.regex.description" = "Comprueba expresiones regulares"; "Reguler expression" = "Expresión regular"; "tool.hyphenremove.title" = "Eliminador de guiones"; "tool.hyphenremove.mintitle" = "Eliminador de guiones"; "tool.hyphenremove.description" = "Elimina guiones de textos copiados de un PDF"; "tool.imageoptim.title" = "Compresor de PNG / JPEG"; "tool.imageoptim.mintitle" = "Compresor de PNG / JPEG"; "tool.imageoptim.description" = "Optimizador de JPEG y PNG sin perdida"; "Optimize Level" = "Nivel de la optimización"; "Low" = "Bajo"; "Medium" = "Medio"; "High" = "Alto"; "Very High (Slow)" = "Muy alto (lento)"; "tool.pdfgen.title" = "Generador de PDFs"; "tool.pdfgen.mintitle" = "Generador de PDFs"; "tool.pdfgen.description" = "Genera un PDF de multiples imágenes"; "Images" = "Imágenes"; "Size" = "Tamaño"; "Generate PDF" = "Generar PDF"; "Page" = "Página"; "tool.imageconvert.title" = "Conversor de imágenes"; "tool.imageconvert.mintitle" = "Conversor de imágenes"; "tool.imageconvert.description" = "Convierte o redimensiona imágenes"; "PNG Format" = "Formato PNG"; "JPEG Format" = "Formato JPEG"; "TIFF Format" = "Formato TIFF"; "GIF Format" = "Formato GIF"; "Scale to Fill" = "Scale to Fill"; "Scale to Fit" = "Scale to Fit"; "Image Format" = "Formato de imágen"; "Resize" = "Redimensionar"; "Converted Images" = "Imágenes convertidas"; "Scale" = "Escala"; "Size" = "Tamaño"; ================================================ FILE: DevToys/DevToys/Resource/es.lproj/Main.strings ================================================ /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */ "1UK-8n-QPP.title" = "Personalizar barra de herramientas…"; /* Class = "NSMenuItem"; title = "DevToys"; ObjectID = "1Xt-HY-uBw"; */ "1Xt-HY-uBw.title" = "DevToys"; /* Class = "NSMenu"; title = "Find"; ObjectID = "1b7-l0-nxx"; */ "1b7-l0-nxx.title" = "Buscar"; /* Class = "NSMenuItem"; title = "Lower"; ObjectID = "1tx-W0-xDw"; */ "1tx-W0-xDw.title" = "Disminuir"; /* Class = "NSMenuItem"; title = "Raise"; ObjectID = "2h7-ER-AoG"; */ "2h7-ER-AoG.title" = "Aumentar"; /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */ "2oI-Rn-ZJC.title" = "Transformaciones"; /* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */ "3IN-sU-3Bg.title" = "Ortografía"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "3Om-Ey-2VK"; */ "3Om-Ey-2VK.title" = "Usar valor por defecto"; /* Class = "NSMenu"; title = "Speech"; ObjectID = "3rS-ZA-NoH"; */ "3rS-ZA-NoH.title" = "Iniciar dictado..."; /* Class = "NSMenuItem"; title = "Tighten"; ObjectID = "46P-cB-AYj"; */ "46P-cB-AYj.title" = "Apertar"; /* Class = "NSMenuItem"; title = "Find"; ObjectID = "4EN-yA-p0u"; */ "4EN-yA-p0u.title" = "Buscar"; /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */ "4J7-dP-txa.title" = "Usar pantalla completa"; /* Class = "NSMenuItem"; title = "Quit DevToys"; ObjectID = "4sb-4s-VLi"; */ "4sb-4s-VLi.title" = "Salir de DevToys"; /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ "5QF-Oa-p0T.title" = "Editar"; /* Class = "NSMenuItem"; title = "Copy Style"; ObjectID = "5Vv-lz-BsD"; */ "5Vv-lz-BsD.title" = "Copiar estilo"; /* Class = "NSMenuItem"; title = "About DevToys"; ObjectID = "5kV-Vb-QxS"; */ "5kV-Vb-QxS.title" = "Acerca de DevToys"; /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ "6dh-zS-Vam.title" = "Rehacer"; /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */ "78Y-hA-62v.title" = "Corregir ortografía automáticamente"; /* Class = "NSMenu"; title = "Writing Direction"; ObjectID = "8mr-sm-Yjd"; */ "8mr-sm-Yjd.title" = "Dirección de escritura"; /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */ "9ic-FL-obx.title" = "Sustituciones"; /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */ "9yt-4B-nSM.title" = "Copiar/Pegar inteligente"; /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ "AYu-sK-qS6.title" = "Menú Principal"; /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ "BOF-NM-1cW.title" = "Preferencias..."; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "BgM-ve-c93"; */ "BgM-ve-c93.title" = "\tIzquierda a derecha"; /* Class = "NSMenuItem"; title = "Save As…"; ObjectID = "Bw7-FT-i3A"; */ "Bw7-FT-i3A.title" = "Guardar como..."; /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */ "DVo-aG-piG.title" = "Cerrar"; /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */ "Dv1-io-Yv7.title" = "Ortografía y gramática"; /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ "F2S-fz-NVQ.title" = "Ayuda"; /* Class = "NSMenuItem"; title = "DevToys Help"; ObjectID = "FKE-Sm-Kum"; */ "FKE-Sm-Kum.title" = "Ayuda de DevToys"; /* Class = "NSMenuItem"; title = "Text"; ObjectID = "Fal-I4-PZk"; */ "Fal-I4-PZk.title" = "Texto"; /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */ "FeM-D8-WVr.title" = "Sustituciones"; /* Class = "NSMenuItem"; title = "Bold"; ObjectID = "GB9-OM-e27"; */ "GB9-OM-e27.title" = "Negrita"; /* Class = "NSMenu"; title = "Format"; ObjectID = "GEO-Iw-cKr"; */ "GEO-Iw-cKr.title" = "Formato"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "GUa-eO-cwY"; */ "GUa-eO-cwY.title" = "Usar valor por defecto"; /* Class = "NSMenuItem"; title = "Font"; ObjectID = "Gi5-1S-RQB"; */ "Gi5-1S-RQB.title" = "Fuente"; /* Class = "NSMenuItem"; title = "Writing Direction"; ObjectID = "H1b-Si-o9J"; */ "H1b-Si-o9J.title" = "Dirección de escritura"; /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ "H8h-7b-M4v.title" = "Ver"; /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */ "HFQ-gK-NFA.title" = "Reemplazar texto"; /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */ "HFo-cy-zxI.title" = "Mostrar ortografía y gramática"; /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ "HyV-fh-RgO.title" = "Ver"; /* Class = "NSMenuItem"; title = "Subscript"; ObjectID = "I0S-gh-46l"; */ "I0S-gh-46l.title" = "Subíndice"; /* Class = "NSMenuItem"; title = "Open…"; ObjectID = "IAo-SY-fd9"; */ "IAo-SY-fd9.title" = "Abrir..."; /* Class = "NSWindow"; title = "DevToys"; ObjectID = "IQv-IB-iLA"; */ "IQv-IB-iLA.title" = "DevToys"; /* Class = "NSMenuItem"; title = "Justify"; ObjectID = "J5U-5w-g23"; */ "J5U-5w-g23.title" = "Justificar"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "J7y-lM-qPV"; */ "J7y-lM-qPV.title" = "Sin estilos"; /* Class = "NSMenuItem"; title = "Revert to Saved"; ObjectID = "KaW-ft-85H"; */ "KaW-ft-85H.title" = "Volver al guardado anterior"; /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ "Kd2-mp-pUS.title" = "Mostrar todo"; /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ "LE2-aR-0XJ.title" = "Traer todo al frente"; /* Class = "NSMenuItem"; title = "Paste Ruler"; ObjectID = "LVM-kO-fVI"; */ "LVM-kO-fVI.title" = "Pegar regla"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "Lbh-J2-qVU"; */ "Lbh-J2-qVU.title" = "\tIzquierda a derecha"; /* Class = "NSMenuItem"; title = "Copy Ruler"; ObjectID = "MkV-Pr-PK5"; */ "MkV-Pr-PK5.title" = "Copiar regla"; /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ "NMo-om-nkz.title" = "Servicios"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "Nop-cj-93Q"; */ "Nop-cj-93Q.title" = "\tValor por defecto"; /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ "OY7-WF-poV.title" = "Minimizar"; /* Class = "NSMenuItem"; title = "Baseline"; ObjectID = "OaQ-X3-Vso"; */ "OaQ-X3-Vso.title" = "Línea base"; /* Class = "NSMenuItem"; title = "Hide DevToys"; ObjectID = "Olw-nP-bQN"; */ "Olw-nP-bQN.title" = "Ocultar DevToys"; /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "OwM-mh-QMV"; */ "OwM-mh-QMV.title" = "Encontrar anterior"; /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "Oyz-dy-DGm"; */ "Oyz-dy-DGm.title" = "Parar dictado"; /* Class = "NSMenuItem"; title = "Bigger"; ObjectID = "Ptp-SP-VEL"; */ "Ptp-SP-VEL.title" = "Aumentar"; /* Class = "NSMenuItem"; title = "Show Fonts"; ObjectID = "Q5e-8K-NDq"; */ "Q5e-8K-NDq.title" = "Mostrar fuentes"; /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ "R4o-n2-Eq4.title" = "Zoom"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "RB4-Sm-HuC"; */ "RB4-Sm-HuC.title" = "\tDerecha a izquierda"; /* Class = "NSMenuItem"; title = "Superscript"; ObjectID = "Rqc-34-cIF"; */ "Rqc-34-cIF.title" = "Superíndice"; /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ "Ruw-6m-B2m.title" = "Seleccionar todo"; /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "S0p-oC-mLd"; */ "S0p-oC-mLd.title" = "Saltar a la selección"; /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ "Td7-aD-5lo.title" = "Ventana"; /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */ "UEZ-Bs-lqG.title" = "Capitalizar"; /* Class = "NSMenuItem"; title = "Center"; ObjectID = "VIY-Ag-zcb"; */ "VIY-Ag-zcb.title" = "Centrar"; /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ "Vdr-fp-XzO.title" = "Ocultar otros"; /* Class = "NSMenuItem"; title = "Italic"; ObjectID = "Vjx-xi-njq"; */ "Vjx-xi-njq.title" = "Itálica"; /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ "W48-6f-4Dl.title" = "Editar"; /* Class = "NSMenuItem"; title = "Underline"; ObjectID = "WRG-CD-K1S"; */ "WRG-CD-K1S.title" = "Subrayar"; /* Class = "NSMenuItem"; title = "New"; ObjectID = "Was-JA-tGl"; */ "Was-JA-tGl.title" = "Nueva ventana"; /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ "WeT-3V-zwk.title" = "Pegar y combinar estilo"; /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "Xz5-n4-O0W"; */ "Xz5-n4-O0W.title" = "Buscar..."; /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "YEy-JH-Tfz"; */ "YEy-JH-Tfz.title" = "Buscar y reemplazar..."; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "YGs-j5-SAR"; */ "YGs-j5-SAR.title" = "\tValor por defecto"; /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "Ynk-f8-cLZ"; */ "Ynk-f8-cLZ.title" = "Comenzar dictado"; /* Class = "NSMenuItem"; title = "Align Left"; ObjectID = "ZM1-6Q-yy1"; */ "ZM1-6Q-yy1.title" = "Alinear a la izquierda"; /* Class = "NSMenuItem"; title = "Paragraph"; ObjectID = "ZvO-Gk-QUH"; */ "ZvO-Gk-QUH.title" = "Parrafo"; /* Class = "NSMenuItem"; title = "Print…"; ObjectID = "aTl-1u-JFS"; */ "aTl-1u-JFS.title" = "Imprimir..."; /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ "aUF-d1-5bR.title" = "Ventana"; /* Class = "NSMenu"; title = "Font"; ObjectID = "aXa-aM-Jaq"; */ "aXa-aM-Jaq.title" = "Fuente"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "agt-UL-0e3"; */ "agt-UL-0e3.title" = "Usar valor por defecto"; /* Class = "NSMenuItem"; title = "Show Colors"; ObjectID = "bgn-CT-cEk"; */ "bgn-CT-cEk.title" = "Mostrar colores"; /* Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu"; */ "bib-Uj-vzu.title" = "Archivo"; /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "buJ-ug-pKt"; */ "buJ-ug-pKt.title" = "Usar selección para buscar"; /* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */ "c8a-y6-VQd.title" = "Transformaciones"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "cDB-IK-hbR"; */ "cDB-IK-hbR.title" = "Sin estilos"; /* Class = "NSMenuItem"; title = "Selection"; ObjectID = "cqv-fj-IhA"; */ "cqv-fj-IhA.title" = "Selección"; /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */ "cwL-P1-jid.title" = "Enlaces inteligentes"; /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */ "d9M-CD-aMd.title" = "Convertir a minúsculas"; /* Class = "NSMenu"; title = "Text"; ObjectID = "d9c-me-L2H"; */ "d9c-me-L2H.title" = "Texto"; /* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */ "dMs-cI-mzQ.title" = "Archivo"; /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ "dRJ-4n-Yzg.title" = "Deshacer"; /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ "gVA-U4-sdL.title" = "Pegar"; /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */ "hQb-2v-fYv.title" = "Citas inteligentes"; /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */ "hz2-CU-CR7.title" = "Comprobar el documento ahora"; /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ "hz9-B4-Xy5.title" = "Servicios"; /* Class = "NSMenuItem"; title = "Smaller"; ObjectID = "i1d-Er-qST"; */ "i1d-Er-qST.title" = "Disminuir"; /* Class = "NSMenu"; title = "Baseline"; ObjectID = "ijk-EB-dga"; */ "ijk-EB-dga.title" = "Línea base"; /* Class = "NSMenuItem"; title = "Kern"; ObjectID = "jBQ-r6-VK2"; */ "jBQ-r6-VK2.title" = "Interletraje"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "jFq-tB-4Kx"; */ "jFq-tB-4Kx.title" = "\tDerecha a izquierda"; /* Class = "NSMenuItem"; title = "Format"; ObjectID = "jxT-CU-nIS"; */ "jxT-CU-nIS.title" = "Formato"; /* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */ "kIP-vf-haE.title" = "Mostrar barra lateral"; /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */ "mK6-2p-4JG.title" = "Comprobar la gramática con la ortografía"; /* Class = "NSMenuItem"; title = "Ligatures"; ObjectID = "o6e-r0-MWq"; */ "o6e-r0-MWq.title" = "Ligaduras"; /* Class = "NSMenu"; title = "Open Recent"; ObjectID = "oas-Oc-fiZ"; */ "oas-Oc-fiZ.title" = "Abrir reciente"; /* Class = "NSMenuItem"; title = "Loosen"; ObjectID = "ogc-rX-tC1"; */ "ogc-rX-tC1.title" = "Soltar"; /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ "pa3-QI-u2k.title" = "Eliminar"; /* Class = "NSMenuItem"; title = "Save…"; ObjectID = "pxx-59-PXV"; */ "pxx-59-PXV.title" = "Guardar..."; /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "q09-fT-Sye"; */ "q09-fT-Sye.title" = "Encontrar próximo"; /* Class = "NSMenuItem"; title = "Page Setup…"; ObjectID = "qIS-W8-SiK"; */ "qIS-W8-SiK.title" = "Configuración de página..."; /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */ "rbD-Rh-wIN.title" = "Revisar la ortografía mientras se escribe"; /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */ "rgM-f4-ycn.title" = "Guiones inteligentes"; /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */ "snW-S8-Cw5.title" = "Mostrar barra de herramientas"; /* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */ "tRr-pd-1PS.title" = "Detectores de datos"; /* Class = "NSMenuItem"; title = "Open Recent"; ObjectID = "tXI-mr-wws"; */ "tXI-mr-wws.title" = "Abrir reciente"; /* Class = "NSMenu"; title = "Kern"; ObjectID = "tlD-Oa-oAM"; */ "tlD-Oa-oAM.title" = "Interletraje"; /* Class = "NSMenu"; title = "DevToys"; ObjectID = "uQy-DD-JDr"; */ "uQy-DD-JDr.title" = "DevToys"; /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ "uRl-iY-unG.title" = "Cortar"; /* Class = "NSMenuItem"; title = "Paste Style"; ObjectID = "vKC-jM-MkH"; */ "vKC-jM-MkH.title" = "Pegar estilo"; /* Class = "NSMenuItem"; title = "Show Ruler"; ObjectID = "vLm-3I-IUL"; */ "vLm-3I-IUL.title" = "Mostrar regla"; /* Class = "NSMenuItem"; title = "Clear Menu"; ObjectID = "vNY-rz-j42"; */ "vNY-rz-j42.title" = "Limpiar menú"; /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */ "vmV-6d-7jI.title" = "Convertir a mayúsculas"; /* Class = "NSMenu"; title = "Ligatures"; ObjectID = "w0m-vy-SC9"; */ "w0m-vy-SC9.title" = "Ligaduras"; /* Class = "NSMenuItem"; title = "Align Right"; ObjectID = "wb2-vD-lq4"; */ "wb2-vD-lq4.title" = "Alinear a la derecha"; /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ "wpr-3q-Mcd.title" = "Ayuda"; /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ "x3v-GG-iWU.title" = "Copiar"; /* Class = "NSMenuItem"; title = "Use All"; ObjectID = "xQD-1f-W4t"; */ "xQD-1f-W4t.title" = "Usar todos"; /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "xrE-MZ-jX0"; */ "xrE-MZ-jX0.title" = "Iniciar dictado..."; /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */ "z6F-FW-3nz.title" = "Mostrar substituciones"; ================================================ FILE: DevToys/DevToys/Resource/ja.lproj/Localizable.strings ================================================ /* Localizable.strings DevToys Created by yuki on 2022/02/13. */ // - General - "Configuration" = "設定"; "Format" = "フォーマット"; "Pretty" = "美しく"; "Minified" = "小さく"; "Encoded" = "エンコード"; "Decoded" = "デコード"; "File" = "ファイル"; "Text" = "テキスト"; "Copy" = "コピー"; "Paste" = "ペースト"; "Copied!" = "コピーされました"; "Pasted!" = "ペーストされました"; "Open" = "開く"; "Export" = "書き出し"; "Import" = "開く..."; "Input" = "入力"; "Output" = "出力"; "No selection" = "選択なし"; "Drop Files Here" = "ここにファイルをドロップ"; "Untitled Tool" = "無名のツール"; "Uppercase" = "大文字"; "Hyphens" = "ハイフン"; "Type" = "種類"; "Length" = "長さ"; "Convert" = "変換"; "Information" = "情報"; "Charactors" = "文字数"; "Words" = "単語数"; "Lines" = "行数"; "Bytes" = "バイト数"; "Clear" = "クリア"; "Delete" = "削除"; "Open in Finder" = "Finderで開く"; "Quick Look" = "クイックルック"; "Default" = "デフォルト"; // - Category - "category.home" = "ホーム"; "category.converters" = "変換"; "category.encoders_decoders" = "エンコード / デコード"; "category.formatters" = "フォーマット"; "category.generators" = "生成"; "category.text" = "テキスト"; "category.graphic" = "画像"; "Media" = "メディア"; // - Tool - "tool.home.title" = "ホーム"; "tool.home.mintitle" = "ホーム"; "tool.home.description" = "全てのツールをここで探せます"; "tool.jsonyaml.title" = "JSONとYamlの変換"; "tool.jsonyaml.mintitle" = "JSON/Yaml"; "tool.jsonyaml.description" = "JSONとYamlを相互に変換します"; "tool.numbase.title" = "基数の変換"; "tool.numbase.mintitle" = "基数の変換"; "tool.numbase.description" = "数値の進法を変換します"; "Format Number" = "数値をフォーマット"; "Decimal" = "10進数"; "Hexdecimal" = "16進数"; "Octal" = "8進数"; "Binary" = "2進数"; "tool.date.title" = "日付の変換"; "tool.date.mintitle" = "日付の変換"; "tool.date.description" = "日付の表現を変換します"; "Now" = "現在"; "Date" = "日付"; "Unix Time" = "Unix時"; "ISO 8601" = "ISO 8601"; "Calender" = "カレンダー"; "tool.html.title" = "HTMLのエンコード・デコード"; "tool.html.mintitle" = "HTML"; "tool.html.description" = "文字をHTMLエスケープ・アンエスケープします"; "tool.url.title" = "URLのエンコード・デコード"; "tool.url.mintitle" = "URL"; "tool.url.description" = "文字をURLパーセントエンコード・逆パーセントエンコードします"; "tool.base64.title" = "Base64エンコード・デコード"; "tool.base64.mintitle" = "Base64"; "tool.base64.description" = "文字・ファイルをBase64にエンコード・デコードします"; "Source Type" = "入力"; "Text Source" = "テキストを入力"; "File Source" = "ファイルを入力"; "tool.jwt.title" = "JWTのデコード"; "tool.jwt.mintitle" = "JWT"; "tool.jwt.description" = "JWTのヘッダ・ペイロードをデコードします"; "JWT Token" = "JWTトークン"; "Header" = "ヘッダ"; "Payload" = "ペイロード"; "tool.jsonformat.title" = "JSONのフォーマット"; "tool.jsonformat.mintitle" = "JSON"; "tool.jsonformat.description" = "JSONデータを美しく表示したり、最小化します"; "Indentation" = "インデント"; "2 Spaces" = "2スペース"; "4 Spaces" = "4スペース"; "1 Tab" = "1タブ"; "tool.xmlformat.title" = "XMLのフォーマット"; "tool.xmlformat.mintitle" = "XML"; "tool.xmlformat.description" = "XMLデータを美しく表示したり、最小化します"; "HTML Document" = "HTMLドキュメント"; "XML Document" = "XMLドキュメント"; "Document Type" = "ドキュメントの種類"; "Auto Fix Document" = "自動修正"; "Pretty Document" = "読みやすい表示"; "tool.hashgen.title" = "Hashを生成"; "tool.hashgen.mintitle" = "Hash"; "tool.hashgen.description" = "テキストのMD5・SHA1・SHA256・SHA512を計算します"; "tool.uuidgen.title" = "UUIDを生成"; "tool.uuidgen.mintitle" = "UUID"; "tool.uuidgen.description" = "UUIDsを生成します"; "Generate UUIDs" = "UUIDを生成"; "tool.ligen.title" = "Lorem Ipsumを生成"; "tool.ligen.mintitle" = "Lorem Ipsum"; "tool.ligen.description" = "Lorem Ipsumの単語・文・段落を生成します"; "Type of generating Lorem Ipsum" = "生成するLorem Ipsumの種類"; "Length of generating Lorem Ipsum" = "生成するLorem Ipsumの長さ"; "Words" = "文字"; "Sentences" = "文章"; "Paragraphs" = "段落"; "tool.checksum.title" = "Checksumを生成"; "tool.checksum.mintitle" = "Checksum"; "tool.checksum.description" = "ファイルのChecksumを生成・検証します"; "Output Comparer" = "出力との比較"; "Hash Algorithm" = "ハッシュのアルゴリズム"; "Select which algorithm you want to use" = "使いたいアルゴリズムを選んでください"; "tool.textinspect.title" = "テキストデータと文字変換"; "tool.textinspect.mintitle" = "データと文字変換"; "tool.textinspect.description" = "テキストのデータの解析・文字変換を行います"; "tool.regex.title" = "正規表現のテスト"; "tool.regex.mintitle" = "正規表現"; "tool.regex.description" = "正規表現をテストします"; "Reguler expression" = "正規表現"; "tool.hyphenremove.title" = "ハイフン削除"; "tool.hyphenremove.mintitle" = "ハイフン削除"; "tool.hyphenremove.description" = "PDFテキストからコピーされたハイフンを削除します"; "tool.imageoptim.title" = "PNG・JPEG最適化"; "tool.imageoptim.mintitle" = "PNG・JPEG最適化"; "tool.imageoptim.description" = "PNG・JPEG形式の画像を最適化してファイルサイズを小さくします"; "Optimize Level" = "最適化レベル"; "Low" = "低レベル"; "Medium" = "中レベル"; "High" = "高レベル"; "Very High (Slow)" = "非常に高レベル (時間がかかります)"; "tool.pdfgen.title" = "画像をPDF化"; "tool.pdfgen.mintitle" = "画像をPDF化"; "tool.pdfgen.description" = "複数枚の画像を1つのPDFファイルにまとめます"; "Images" = "画像"; "Size" = "サイズ"; "Generate PDF" = "PDFを作成"; "Page" = "ページ"; "tool.imageconvert.title" = "画像の変換"; "tool.imageconvert.mintitle" = "画像の変換"; "tool.imageconvert.description" = "画像のフォーマットの変換やリサイズを行います"; "PNG Format" = "PNG形式"; "JPEG Format" = "JPEG形式"; "TIFF Format" = "TIFF形式"; "GIF Format" = "GIF形式"; "Scale to Fill" = "サイズを埋める"; "Scale to Fit" = "サイズ内に収める"; "Image Format" = "フォーマット"; "Resize" = "リサイズ"; "Converted Images" = "変換した画像"; "Scale" = "大きさの変換"; "Size" = "サイズ"; "Override original file" = "元のファイルを上書き"; "QR Code Generator" = "QRコード作成"; "Create a QR code from text" = "テキストからQRコードを作成します"; "Correction Level" = "誤り復元"; "QR Code" = "QRコード"; "Settings" = "設定"; "Setting of application" = "アプリの設定を行います"; "App Theme" = "アプリのテーマ"; "Select which app theme to display" = "表示に用いるテーマの選択"; "Use system setting" = "自動"; "Light mode" = "ライト"; "Dark mode" = "ダーク"; "Color Picker" = "カラーピッカー"; "Picker the color and copy components" = "色を選択してフォーマットした文字列をコピーします"; "HSB Box" = "HSBボックス"; "HSB Circle" = "HSBサークル"; "HSB Circle and Bars" = "HSB バー&サークル"; "Pick Color" = "色を選択"; "Picker Type" = "選択のタイプ"; "Color Hex" = "Hex値"; "Color Copy" = "色をコピー"; "Color Copy Type" = "型"; "Gif Converter" = "Gifへの変換"; "Convert a movie to an animated GIF file" = "動画をアニメーションGifに変換します"; "Width" = "幅"; "The width of the Gif file" = "Gifファイルの横幅 (高さは自動決定されます)"; "FPS" = "FPS"; "FPS of the Gif file to be exported" = "書き出すGifファイルのFPS"; "Remove source file" = "元ファイルを削除"; "Whether to delete the source file after exporting a Gif" = "Gifファイル書き出し後に元ファイルを削除するか"; "Audio Converter" = "音声フォーマット変換"; "Convert audio from one format to another" = "音声ファイルのフォーマット・音質の変更を行います"; "Whether to delete the source file after exporting a Audio" = "ファイル書き出し後に元ファイルを削除するか"; "Starting..." = "初期化中..."; "Complete" = "完了"; "Convert Failed" = "変換失敗"; "Text Diff" = "テキストの比較"; "Compare two Text and display Diff" = "2つのテキストを比べて差分を表示します"; "By Characters" = "文字"; "By Words" = "単語"; "By Lines" = "行"; "Input 1" = "元のテキスト"; "Input 2" = "変更後のテキスト"; "Diff Style" = "差分の取り方"; ================================================ FILE: DevToys/DevToys/Resource/ja.lproj/Main.strings ================================================ /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */ "1UK-8n-QPP.title" = "Customize Toolbar…"; /* Class = "NSMenuItem"; title = "DevToys"; ObjectID = "1Xt-HY-uBw"; */ "1Xt-HY-uBw.title" = "DevToys"; /* Class = "NSMenu"; title = "Find"; ObjectID = "1b7-l0-nxx"; */ "1b7-l0-nxx.title" = "Find"; /* Class = "NSMenuItem"; title = "Lower"; ObjectID = "1tx-W0-xDw"; */ "1tx-W0-xDw.title" = "Lower"; /* Class = "NSMenuItem"; title = "Raise"; ObjectID = "2h7-ER-AoG"; */ "2h7-ER-AoG.title" = "Raise"; /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */ "2oI-Rn-ZJC.title" = "Transformations"; /* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */ "3IN-sU-3Bg.title" = "Spelling"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "3Om-Ey-2VK"; */ "3Om-Ey-2VK.title" = "Use Default"; /* Class = "NSMenu"; title = "Speech"; ObjectID = "3rS-ZA-NoH"; */ "3rS-ZA-NoH.title" = "Speech"; /* Class = "NSMenuItem"; title = "Tighten"; ObjectID = "46P-cB-AYj"; */ "46P-cB-AYj.title" = "Tighten"; /* Class = "NSMenuItem"; title = "Find"; ObjectID = "4EN-yA-p0u"; */ "4EN-yA-p0u.title" = "Find"; /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */ "4J7-dP-txa.title" = "Enter Full Screen"; /* Class = "NSMenuItem"; title = "Quit DevToys"; ObjectID = "4sb-4s-VLi"; */ "4sb-4s-VLi.title" = "Quit DevToys"; /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ "5QF-Oa-p0T.title" = "Edit"; /* Class = "NSMenuItem"; title = "Copy Style"; ObjectID = "5Vv-lz-BsD"; */ "5Vv-lz-BsD.title" = "Copy Style"; /* Class = "NSMenuItem"; title = "About DevToys"; ObjectID = "5kV-Vb-QxS"; */ "5kV-Vb-QxS.title" = "About DevToys"; /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ "6dh-zS-Vam.title" = "Redo"; /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */ "78Y-hA-62v.title" = "Correct Spelling Automatically"; /* Class = "NSMenu"; title = "Writing Direction"; ObjectID = "8mr-sm-Yjd"; */ "8mr-sm-Yjd.title" = "Writing Direction"; /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */ "9ic-FL-obx.title" = "Substitutions"; /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */ "9yt-4B-nSM.title" = "Smart Copy/Paste"; /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ "AYu-sK-qS6.title" = "Main Menu"; /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ "BOF-NM-1cW.title" = "Preferences…"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "BgM-ve-c93"; */ "BgM-ve-c93.title" = "\tLeft to Right"; /* Class = "NSMenuItem"; title = "Save As…"; ObjectID = "Bw7-FT-i3A"; */ "Bw7-FT-i3A.title" = "Save As…"; /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */ "DVo-aG-piG.title" = "Close"; /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */ "Dv1-io-Yv7.title" = "Spelling and Grammar"; /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ "F2S-fz-NVQ.title" = "Help"; /* Class = "NSMenuItem"; title = "DevToys Help"; ObjectID = "FKE-Sm-Kum"; */ "FKE-Sm-Kum.title" = "DevToys Help"; /* Class = "NSMenuItem"; title = "Text"; ObjectID = "Fal-I4-PZk"; */ "Fal-I4-PZk.title" = "Text"; /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */ "FeM-D8-WVr.title" = "Substitutions"; /* Class = "NSMenuItem"; title = "Bold"; ObjectID = "GB9-OM-e27"; */ "GB9-OM-e27.title" = "Bold"; /* Class = "NSMenu"; title = "Format"; ObjectID = "GEO-Iw-cKr"; */ "GEO-Iw-cKr.title" = "Format"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "GUa-eO-cwY"; */ "GUa-eO-cwY.title" = "Use Default"; /* Class = "NSMenuItem"; title = "Font"; ObjectID = "Gi5-1S-RQB"; */ "Gi5-1S-RQB.title" = "Font"; /* Class = "NSMenuItem"; title = "Writing Direction"; ObjectID = "H1b-Si-o9J"; */ "H1b-Si-o9J.title" = "Writing Direction"; /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ "H8h-7b-M4v.title" = "View"; /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */ "HFQ-gK-NFA.title" = "Text Replacement"; /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */ "HFo-cy-zxI.title" = "Show Spelling and Grammar"; /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ "HyV-fh-RgO.title" = "View"; /* Class = "NSMenuItem"; title = "Subscript"; ObjectID = "I0S-gh-46l"; */ "I0S-gh-46l.title" = "Subscript"; /* Class = "NSMenuItem"; title = "Open…"; ObjectID = "IAo-SY-fd9"; */ "IAo-SY-fd9.title" = "Open…"; /* Class = "NSWindow"; title = "DevToys"; ObjectID = "IQv-IB-iLA"; */ "IQv-IB-iLA.title" = "DevToys"; /* Class = "NSMenuItem"; title = "Justify"; ObjectID = "J5U-5w-g23"; */ "J5U-5w-g23.title" = "Justify"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "J7y-lM-qPV"; */ "J7y-lM-qPV.title" = "Use None"; /* Class = "NSMenuItem"; title = "Revert to Saved"; ObjectID = "KaW-ft-85H"; */ "KaW-ft-85H.title" = "Revert to Saved"; /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ "Kd2-mp-pUS.title" = "Show All"; /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ "LE2-aR-0XJ.title" = "Bring All to Front"; /* Class = "NSMenuItem"; title = "Paste Ruler"; ObjectID = "LVM-kO-fVI"; */ "LVM-kO-fVI.title" = "Paste Ruler"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "Lbh-J2-qVU"; */ "Lbh-J2-qVU.title" = "\tLeft to Right"; /* Class = "NSMenuItem"; title = "Copy Ruler"; ObjectID = "MkV-Pr-PK5"; */ "MkV-Pr-PK5.title" = "Copy Ruler"; /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ "NMo-om-nkz.title" = "Services"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "Nop-cj-93Q"; */ "Nop-cj-93Q.title" = "\tDefault"; /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ "OY7-WF-poV.title" = "Minimize"; /* Class = "NSMenuItem"; title = "Baseline"; ObjectID = "OaQ-X3-Vso"; */ "OaQ-X3-Vso.title" = "Baseline"; /* Class = "NSMenuItem"; title = "Hide DevToys"; ObjectID = "Olw-nP-bQN"; */ "Olw-nP-bQN.title" = "Hide DevToys"; /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "OwM-mh-QMV"; */ "OwM-mh-QMV.title" = "Find Previous"; /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "Oyz-dy-DGm"; */ "Oyz-dy-DGm.title" = "Stop Speaking"; /* Class = "NSMenuItem"; title = "Bigger"; ObjectID = "Ptp-SP-VEL"; */ "Ptp-SP-VEL.title" = "Bigger"; /* Class = "NSMenuItem"; title = "Show Fonts"; ObjectID = "Q5e-8K-NDq"; */ "Q5e-8K-NDq.title" = "Show Fonts"; /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ "R4o-n2-Eq4.title" = "Zoom"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "RB4-Sm-HuC"; */ "RB4-Sm-HuC.title" = "\tRight to Left"; /* Class = "NSMenuItem"; title = "Superscript"; ObjectID = "Rqc-34-cIF"; */ "Rqc-34-cIF.title" = "Superscript"; /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ "Ruw-6m-B2m.title" = "Select All"; /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "S0p-oC-mLd"; */ "S0p-oC-mLd.title" = "Jump to Selection"; /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ "Td7-aD-5lo.title" = "Window"; /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */ "UEZ-Bs-lqG.title" = "Capitalize"; /* Class = "NSMenuItem"; title = "Center"; ObjectID = "VIY-Ag-zcb"; */ "VIY-Ag-zcb.title" = "Center"; /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ "Vdr-fp-XzO.title" = "Hide Others"; /* Class = "NSMenuItem"; title = "Italic"; ObjectID = "Vjx-xi-njq"; */ "Vjx-xi-njq.title" = "Italic"; /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ "W48-6f-4Dl.title" = "Edit"; /* Class = "NSMenuItem"; title = "Underline"; ObjectID = "WRG-CD-K1S"; */ "WRG-CD-K1S.title" = "Underline"; /* Class = "NSMenuItem"; title = "New"; ObjectID = "Was-JA-tGl"; */ "Was-JA-tGl.title" = "New"; /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ "WeT-3V-zwk.title" = "Paste and Match Style"; /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "Xz5-n4-O0W"; */ "Xz5-n4-O0W.title" = "Find…"; /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "YEy-JH-Tfz"; */ "YEy-JH-Tfz.title" = "Find and Replace…"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "YGs-j5-SAR"; */ "YGs-j5-SAR.title" = "\tDefault"; /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "Ynk-f8-cLZ"; */ "Ynk-f8-cLZ.title" = "Start Speaking"; /* Class = "NSMenuItem"; title = "Align Left"; ObjectID = "ZM1-6Q-yy1"; */ "ZM1-6Q-yy1.title" = "Align Left"; /* Class = "NSMenuItem"; title = "Paragraph"; ObjectID = "ZvO-Gk-QUH"; */ "ZvO-Gk-QUH.title" = "Paragraph"; /* Class = "NSMenuItem"; title = "Print…"; ObjectID = "aTl-1u-JFS"; */ "aTl-1u-JFS.title" = "Print…"; /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ "aUF-d1-5bR.title" = "Window"; /* Class = "NSMenu"; title = "Font"; ObjectID = "aXa-aM-Jaq"; */ "aXa-aM-Jaq.title" = "Font"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "agt-UL-0e3"; */ "agt-UL-0e3.title" = "Use Default"; /* Class = "NSMenuItem"; title = "Show Colors"; ObjectID = "bgn-CT-cEk"; */ "bgn-CT-cEk.title" = "Show Colors"; /* Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu"; */ "bib-Uj-vzu.title" = "File"; /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "buJ-ug-pKt"; */ "buJ-ug-pKt.title" = "Use Selection for Find"; /* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */ "c8a-y6-VQd.title" = "Transformations"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "cDB-IK-hbR"; */ "cDB-IK-hbR.title" = "Use None"; /* Class = "NSMenuItem"; title = "Selection"; ObjectID = "cqv-fj-IhA"; */ "cqv-fj-IhA.title" = "Selection"; /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */ "cwL-P1-jid.title" = "Smart Links"; /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */ "d9M-CD-aMd.title" = "Make Lower Case"; /* Class = "NSMenu"; title = "Text"; ObjectID = "d9c-me-L2H"; */ "d9c-me-L2H.title" = "Text"; /* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */ "dMs-cI-mzQ.title" = "File"; /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ "dRJ-4n-Yzg.title" = "Undo"; /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ "gVA-U4-sdL.title" = "Paste"; /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */ "hQb-2v-fYv.title" = "Smart Quotes"; /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */ "hz2-CU-CR7.title" = "Check Document Now"; /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ "hz9-B4-Xy5.title" = "Services"; /* Class = "NSMenuItem"; title = "Smaller"; ObjectID = "i1d-Er-qST"; */ "i1d-Er-qST.title" = "Smaller"; /* Class = "NSMenu"; title = "Baseline"; ObjectID = "ijk-EB-dga"; */ "ijk-EB-dga.title" = "Baseline"; /* Class = "NSMenuItem"; title = "Kern"; ObjectID = "jBQ-r6-VK2"; */ "jBQ-r6-VK2.title" = "Kern"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "jFq-tB-4Kx"; */ "jFq-tB-4Kx.title" = "\tRight to Left"; /* Class = "NSMenuItem"; title = "Format"; ObjectID = "jxT-CU-nIS"; */ "jxT-CU-nIS.title" = "Format"; /* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */ "kIP-vf-haE.title" = "Show Sidebar"; /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */ "mK6-2p-4JG.title" = "Check Grammar With Spelling"; /* Class = "NSMenuItem"; title = "Ligatures"; ObjectID = "o6e-r0-MWq"; */ "o6e-r0-MWq.title" = "Ligatures"; /* Class = "NSMenu"; title = "Open Recent"; ObjectID = "oas-Oc-fiZ"; */ "oas-Oc-fiZ.title" = "Open Recent"; /* Class = "NSMenuItem"; title = "Loosen"; ObjectID = "ogc-rX-tC1"; */ "ogc-rX-tC1.title" = "Loosen"; /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ "pa3-QI-u2k.title" = "Delete"; /* Class = "NSMenuItem"; title = "Save…"; ObjectID = "pxx-59-PXV"; */ "pxx-59-PXV.title" = "Save…"; /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "q09-fT-Sye"; */ "q09-fT-Sye.title" = "Find Next"; /* Class = "NSMenuItem"; title = "Page Setup…"; ObjectID = "qIS-W8-SiK"; */ "qIS-W8-SiK.title" = "Page Setup…"; /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */ "rbD-Rh-wIN.title" = "Check Spelling While Typing"; /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */ "rgM-f4-ycn.title" = "Smart Dashes"; /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */ "snW-S8-Cw5.title" = "Show Toolbar"; /* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */ "tRr-pd-1PS.title" = "Data Detectors"; /* Class = "NSMenuItem"; title = "Open Recent"; ObjectID = "tXI-mr-wws"; */ "tXI-mr-wws.title" = "Open Recent"; /* Class = "NSMenu"; title = "Kern"; ObjectID = "tlD-Oa-oAM"; */ "tlD-Oa-oAM.title" = "Kern"; /* Class = "NSMenu"; title = "DevToys"; ObjectID = "uQy-DD-JDr"; */ "uQy-DD-JDr.title" = "DevToys"; /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ "uRl-iY-unG.title" = "Cut"; /* Class = "NSMenuItem"; title = "Paste Style"; ObjectID = "vKC-jM-MkH"; */ "vKC-jM-MkH.title" = "Paste Style"; /* Class = "NSMenuItem"; title = "Show Ruler"; ObjectID = "vLm-3I-IUL"; */ "vLm-3I-IUL.title" = "Show Ruler"; /* Class = "NSMenuItem"; title = "Clear Menu"; ObjectID = "vNY-rz-j42"; */ "vNY-rz-j42.title" = "Clear Menu"; /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */ "vmV-6d-7jI.title" = "Make Upper Case"; /* Class = "NSMenu"; title = "Ligatures"; ObjectID = "w0m-vy-SC9"; */ "w0m-vy-SC9.title" = "Ligatures"; /* Class = "NSMenuItem"; title = "Align Right"; ObjectID = "wb2-vD-lq4"; */ "wb2-vD-lq4.title" = "Align Right"; /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ "wpr-3q-Mcd.title" = "Help"; /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ "x3v-GG-iWU.title" = "Copy"; /* Class = "NSMenuItem"; title = "Use All"; ObjectID = "xQD-1f-W4t"; */ "xQD-1f-W4t.title" = "Use All"; /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "xrE-MZ-jX0"; */ "xrE-MZ-jX0.title" = "Speech"; /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */ "z6F-FW-3nz.title" = "Show Substitutions"; ================================================ FILE: DevToys/DevToys/Resource/pt-BR.lproj/Localizable.strings ================================================ /* Localizable.strings DevToys Created by daniel bertoldi on 2022/02/14. */ // - General - "Configuration" = "Configuração"; "Format" = "Formatar"; "Pretty" = "Embelezar"; "Minified" = "Minificar"; "Encoded" = "Codificar"; "Decoded" = "Decodificar"; "File" = "Arquivo"; "Text" = "Texto"; "Copy" = "Copiar"; "Paste" = "Colar"; "Copied!" = "Copiado!"; "Pasted!" = "Colado"; "Open" = "Abrir"; "Export" = "Exportar"; "Import" = "Importar"; "Input" = "Entrada"; "Output" = "Saída"; "No selection" = "Sem seleção"; "Drop Files Here" = "Arraste Os Arquivos Aqui"; "Untitled Tool" = "Ferramenta Sem Nome"; "Uppercase" = "Maíusculo"; "Hyphens" = "Hífens"; "Type" = "Tipo"; "Length" = "Comprimento"; "Convert" = "Converter"; "Information" = "Informação"; "Clear" = "Limpar"; "Delete" = "Deletar"; // - Category - "category.home" = "Menu Principal"; "category.converters" = "Conversores"; "category.encoders_decoders" = "Codificadores / Decodificadores"; "category.formatters" = "Formatadores"; "category.generators" = "Geradores"; "category.text" = "Texto"; "category.graphic" = "Gráficos"; // - Tool - "tool.home.title" = "Menu Principal"; "tool.home.mintitle" = "Menu Principal"; "tool.home.description" = "Procure por ferramentas aqui."; "tool.jsonyaml.title" = "Conversor JSON <> Yaml"; "tool.jsonyaml.mintitle" = "JSON <> Yaml"; "tool.jsonyaml.description" = "Converta dados em JSON para YAML e vice-versa"; "tool.numbase.title" = "Conversor de Base Numérica"; "tool.numbase.mintitle" = "Base Numérica"; "tool.numbase.description" = "Converta números de uma base para outra"; "Format Number" = "Formatar Número"; "Decimal" = "Decimal"; "Hexdecimal" = "Hexadecimal"; "Octal" = "Octal"; "Binary" = "Binário"; "tool.date.title" = "Conversor de Data"; "tool.date.mintitle" = "Data"; "tool.date.description" = "Converte um formato de data para outro"; "Now" = "Agora"; "Date" = "Data"; "Unix Time" = "Tempo Unix"; "ISO 8601" = "ISO 8601"; "Calender" = "Calendário"; "tool.html.title" = "Codificador / Decodificador de HTML"; "tool.html.mintitle" = "HTML"; "tool.html.description" = "Codifica ou decodifica todos os caracteres aplicáveis ao seu HTML correspondente"; "tool.url.title" = "Codificador / Decodificador de URL"; "tool.url.mintitle" = "URL"; "tool.url.description" = "Codifica ou decodifica todos os caracteres aplicáveis ao seu URL correspondente"; "tool.base64.title" = "Codificador / Decodificador de Base64"; "tool.base64.mintitle" = "Base64"; "tool.base64.description" = "Codifica e decodifica dados em Base64"; "Source Type" = "Tipo de Fonte"; "Text Source" = "Fonte de Texto"; "File Source" = "Fonte de Arquivo"; "tool.jwt.title" = "Codificador / Decodificador de JWT"; "tool.jwt.mintitle" = "JWT"; "tool.jwt.description" = "Decodifica um payload e assinatura de um header JWT"; "JWT Token" = "Token JWT"; "Header" = "Header"; "Payload" = "Payload"; "tool.jsonformat.title" = "Formatador de JSON"; "tool.jsonformat.mintitle" = "JSON"; "tool.jsonformat.description" = "Indenta ou minifica um JSON"; "Indentation" = "Indentação"; "2 Spaces" = "2 Espaços"; "4 Spaces" = "4 Espaços"; "1 Tab" = "1 Tab"; "tool.xmlformat.title" = "Formatador XML"; "tool.xmlformat.mintitle" = "XML"; "tool.xmlformat.description" = "Indenta ou minifica um XML"; "HTML Document" = "Documento HTML"; "XML Document" = "Documento XML"; "Document Type" = "Tipo de Documento"; "Auto Fix Document" = "Corrigir Documento Automaticamente"; "Pretty Document" = "Embelezar Documento"; "tool.hashgen.title" = "Gerador de Hash"; "tool.hashgen.mintitle" = "Hash"; "tool.hashgen.description" = "Calcula hashes MD5, SHA1, SHA256 e SHA 512 a partir de um texto"; "tool.uuidgen.title" = "Gerador de UUID"; "tool.uuidgen.mintitle" = "UUID"; "tool.uuidgen.description" = "Gera UUIDs"; "Generate UUIDs" = "Gerar UUIDs"; "tool.ligen.title" = "Gerador de Lorem Ipsum"; "tool.ligen.mintitle" = "Lorem Ipsum"; "tool.ligen.description" = "Gera texto de Lorem Ipsum"; "Type of generating Lorem Ipsum" = "Tipo de geração de Lorem Ipsum"; "Length of generating Lorem Ipsum" = "Tamanho do Lorem Ipsum a ser gerado"; "Words" = "Palavras"; "Sentences" = "Frases"; "Paragraphs" = "Parágrafos"; "tool.checksum.title" = "Gerador de Checksum"; "tool.checksum.mintitle" = "Checksum"; "tool.checksum.description" = "Gera ou testa o checksum de um arquivo"; "Output Comparer" = "Comparador de Saída"; "Hash Algorithm" = "Algoritmo de Hash"; "Select which algorithm you want to use" = "Selecione qual algoritmo você quer usar"; "tool.textinspect.title" = "Inspetor de Texto e Conversor de Case"; "tool.textinspect.mintitle" = "Inspetor de Texto e Conversor de Case"; "tool.textinspect.description" = " Analisa o texto e converte para um case diferente"; "Charactors" = "Caracteres"; "Words" = "Palavras"; "Lines" = "Linhas"; "Bytes" = "Bytes"; "tool.regex.title" = "Testador de Regex"; "tool.regex.mintitle" = "Regex"; "tool.regex.description" = "Testar expressão regular"; "Reguler expression" = "Expressão Regular"; "tool.hyphenremove.title" = "Removedor de Hifenação"; "tool.hyphenremove.mintitle" = "Hifenação"; "tool.hyphenremove.description" = "Remove hifenações copiados de um texto PDF"; "tool.imageoptim.title" = "Compressor de PNG / JPEG"; "tool.imageoptim.mintitle" = "Compressor de PNG / JPEG"; "tool.imageoptim.description" = "Otimizador sem perdas de PNG e JPEG"; "Optimize Level" = "Nível de Otimização"; "Low" = "Baixo"; "Medium" = "Médio"; "High" = "Alto"; "Very High (Slow)" = "Muito Alto (Lento)"; "tool.pdfgen.title" = "Gerador de PDF"; "tool.pdfgen.mintitle" = "Gerador de PDF"; "tool.pdfgen.description" = "Gera PDF a partir de múltipas imagens"; "Images" = "Imagens"; "Size" = "Tamanho"; "Generate PDF" = "Gerar PDF"; "Page" = "Página"; "tool.imageconvert.title" = "Conversor de Imagem"; "tool.imageconvert.mintitle" = "Conversor de Imagem"; "tool.imageconvert.description" = "Converte ou redimensiona imagens"; "PNG Format" = "Formato PNG"; "JPEG Format" = "Formato JPEG"; "TIFF Format" = "Formato TIFF"; "GIF Format" = "Formato GIF"; "Scale to Fill" = "Escalar para preencher"; "Scale to Fit" = "Escalar para encaixar"; "Image Format" = "Formatar Imagem"; "Resize" = "Redimensionar"; "Converted Images" = "Imagens Convertidas"; "Scale" = "Escalar"; "Size" = "Tamanho"; ================================================ FILE: DevToys/DevToys/Resource/pt-BR.lproj/Main.strings ================================================ /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */ "1UK-8n-QPP.title" = "Customizar Barra de Ferramentas…"; /* Class = "NSMenuItem"; title = "DevToys"; ObjectID = "1Xt-HY-uBw"; */ "1Xt-HY-uBw.title" = "DevToys"; /* Class = "NSMenu"; title = "Find"; ObjectID = "1b7-l0-nxx"; */ "1b7-l0-nxx.title" = "Procurar"; /* Class = "NSMenuItem"; title = "Lower"; ObjectID = "1tx-W0-xDw"; */ "1tx-W0-xDw.title" = "Diminuir"; /* Class = "NSMenuItem"; title = "Raise"; ObjectID = "2h7-ER-AoG"; */ "2h7-ER-AoG.title" = "Aumentar"; /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */ "2oI-Rn-ZJC.title" = "Transformações"; /* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */ "3IN-sU-3Bg.title" = "Ortografia"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "3Om-Ey-2VK"; */ "3Om-Ey-2VK.title" = "Usar Padrão"; /* Class = "NSMenu"; title = "Speech"; ObjectID = "3rS-ZA-NoH"; */ "3rS-ZA-NoH.title" = "Fala"; /* Class = "NSMenuItem"; title = "Tighten"; ObjectID = "46P-cB-AYj"; */ "46P-cB-AYj.title" = "Apertar"; /* Class = "NSMenuItem"; title = "Find"; ObjectID = "4EN-yA-p0u"; */ "4EN-yA-p0u.title" = "Procurar"; /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */ "4J7-dP-txa.title" = "Entrar em Tela Cheia"; /* Class = "NSMenuItem"; title = "Quit DevToys"; ObjectID = "4sb-4s-VLi"; */ "4sb-4s-VLi.title" = "Sair do DevToys"; /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ "5QF-Oa-p0T.title" = "Editar"; /* Class = "NSMenuItem"; title = "Copy Style"; ObjectID = "5Vv-lz-BsD"; */ "5Vv-lz-BsD.title" = "Copiar Estilo"; /* Class = "NSMenuItem"; title = "About DevToys"; ObjectID = "5kV-Vb-QxS"; */ "5kV-Vb-QxS.title" = "Sobre o DevToys"; /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ "6dh-zS-Vam.title" = "Refazer"; /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */ "78Y-hA-62v.title" = "Corrigir Ortografia Automaticamente"; /* Class = "NSMenu"; title = "Writing Direction"; ObjectID = "8mr-sm-Yjd"; */ "8mr-sm-Yjd.title" = "Direção de Escrita"; /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */ "9ic-FL-obx.title" = "Substituições"; /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */ "9yt-4B-nSM.title" = "Copiar/Colar Inteligente"; /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ "AYu-sK-qS6.title" = "Menu Principal"; /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ "BOF-NM-1cW.title" = "Preferências"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "BgM-ve-c93"; */ "BgM-ve-c93.title" = "\tEsquerda para Direita"; /* Class = "NSMenuItem"; title = "Save As…"; ObjectID = "Bw7-FT-i3A"; */ "Bw7-FT-i3A.title" = "Salvar Como..."; /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */ "DVo-aG-piG.title" = "Fechar"; /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */ "Dv1-io-Yv7.title" = "Ortografia e Gramática"; /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ "F2S-fz-NVQ.title" = "Ajuda"; /* Class = "NSMenuItem"; title = "DevToys Help"; ObjectID = "FKE-Sm-Kum"; */ "FKE-Sm-Kum.title" = "Ajuda do DevToys"; /* Class = "NSMenuItem"; title = "Text"; ObjectID = "Fal-I4-PZk"; */ "Fal-I4-PZk.title" = "Texto"; /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */ "FeM-D8-WVr.title" = "Substituições"; /* Class = "NSMenuItem"; title = "Bold"; ObjectID = "GB9-OM-e27"; */ "GB9-OM-e27.title" = "Negrito"; /* Class = "NSMenu"; title = "Format"; ObjectID = "GEO-Iw-cKr"; */ "GEO-Iw-cKr.title" = "Formatar"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "GUa-eO-cwY"; */ "GUa-eO-cwY.title" = "Usar Padrão"; /* Class = "NSMenuItem"; title = "Font"; ObjectID = "Gi5-1S-RQB"; */ "Gi5-1S-RQB.title" = "Fonte"; /* Class = "NSMenuItem"; title = "Writing Direction"; ObjectID = "H1b-Si-o9J"; */ "H1b-Si-o9J.title" = "Direção de Escrita"; /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ "H8h-7b-M4v.title" = "Exibir"; /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */ "HFQ-gK-NFA.title" = "Substituir Texto"; /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */ "HFo-cy-zxI.title" = "Mostrar Ortografia e Gramática"; /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ "HyV-fh-RgO.title" = "Exibir"; /* Class = "NSMenuItem"; title = "Subscript"; ObjectID = "I0S-gh-46l"; */ "I0S-gh-46l.title" = "Subscrito"; /* Class = "NSMenuItem"; title = "Open…"; ObjectID = "IAo-SY-fd9"; */ "IAo-SY-fd9.title" = "Abrir..."; /* Class = "NSWindow"; title = "DevToys"; ObjectID = "IQv-IB-iLA"; */ "IQv-IB-iLA.title" = "DevToys"; /* Class = "NSMenuItem"; title = "Justify"; ObjectID = "J5U-5w-g23"; */ "J5U-5w-g23.title" = "Justificar"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "J7y-lM-qPV"; */ "J7y-lM-qPV.title" = "Não Usar Nenhum"; /* Class = "NSMenuItem"; title = "Revert to Saved"; ObjectID = "KaW-ft-85H"; */ "KaW-ft-85H.title" = "Reverter Para Salvo"; /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ "Kd2-mp-pUS.title" = "Mostrar Tudo"; /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ "LE2-aR-0XJ.title" = "Trazer Para Frente"; /* Class = "NSMenuItem"; title = "Paste Ruler"; ObjectID = "LVM-kO-fVI"; */ "LVM-kO-fVI.title" = "Colar Régua"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "Lbh-J2-qVU"; */ "Lbh-J2-qVU.title" = "\tEsquerda Para Direita"; /* Class = "NSMenuItem"; title = "Copy Ruler"; ObjectID = "MkV-Pr-PK5"; */ "MkV-Pr-PK5.title" = "Copiar Régua"; /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ "NMo-om-nkz.title" = "Serviços"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "Nop-cj-93Q"; */ "Nop-cj-93Q.title" = "\tPadrão"; /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ "OY7-WF-poV.title" = "Minimizar"; /* Class = "NSMenuItem"; title = "Baseline"; ObjectID = "OaQ-X3-Vso"; */ "OaQ-X3-Vso.title" = "Linha De Base"; /* Class = "NSMenuItem"; title = "Hide DevToys"; ObjectID = "Olw-nP-bQN"; */ "Olw-nP-bQN.title" = "Esconder DevToys"; /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "OwM-mh-QMV"; */ "OwM-mh-QMV.title" = "Encontrar Anterior"; /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "Oyz-dy-DGm"; */ "Oyz-dy-DGm.title" = "Parar de Falar"; /* Class = "NSMenuItem"; title = "Bigger"; ObjectID = "Ptp-SP-VEL"; */ "Ptp-SP-VEL.title" = "Aumentar"; /* Class = "NSMenuItem"; title = "Show Fonts"; ObjectID = "Q5e-8K-NDq"; */ "Q5e-8K-NDq.title" = "Mostrar Fontes"; /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ "R4o-n2-Eq4.title" = "Zoom"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "RB4-Sm-HuC"; */ "RB4-Sm-HuC.title" = "\tDireita Para Esquerda"; /* Class = "NSMenuItem"; title = "Superscript"; ObjectID = "Rqc-34-cIF"; */ "Rqc-34-cIF.title" = "Superescrito"; /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ "Ruw-6m-B2m.title" = "Selecionar Tudo"; /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "S0p-oC-mLd"; */ "S0p-oC-mLd.title" = "Pular Para a Seleção"; /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ "Td7-aD-5lo.title" = "Janela"; /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */ "UEZ-Bs-lqG.title" = "Capitalizar"; /* Class = "NSMenuItem"; title = "Center"; ObjectID = "VIY-Ag-zcb"; */ "VIY-Ag-zcb.title" = "Centralizar"; /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ "Vdr-fp-XzO.title" = "Esconder Outros"; /* Class = "NSMenuItem"; title = "Italic"; ObjectID = "Vjx-xi-njq"; */ "Vjx-xi-njq.title" = "Itálico"; /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ "W48-6f-4Dl.title" = "Editar"; /* Class = "NSMenuItem"; title = "Underline"; ObjectID = "WRG-CD-K1S"; */ "WRG-CD-K1S.title" = "Sublinhado"; /* Class = "NSMenuItem"; title = "New"; ObjectID = "Was-JA-tGl"; */ "Was-JA-tGl.title" = "Novo"; /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ "WeT-3V-zwk.title" = "Colar e Manter Estilo"; /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "Xz5-n4-O0W"; */ "Xz5-n4-O0W.title" = "Procurar..."; /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "YEy-JH-Tfz"; */ "YEy-JH-Tfz.title" = "Procurar e Substituir..."; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "YGs-j5-SAR"; */ "YGs-j5-SAR.title" = "\tPadrão"; /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "Ynk-f8-cLZ"; */ "Ynk-f8-cLZ.title" = "Começar a Falar"; /* Class = "NSMenuItem"; title = "Align Left"; ObjectID = "ZM1-6Q-yy1"; */ "ZM1-6Q-yy1.title" = "Alinhar à Esquerda"; /* Class = "NSMenuItem"; title = "Paragraph"; ObjectID = "ZvO-Gk-QUH"; */ "ZvO-Gk-QUH.title" = "Parágrafo"; /* Class = "NSMenuItem"; title = "Print…"; ObjectID = "aTl-1u-JFS"; */ "aTl-1u-JFS.title" = "Imprimir..."; /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ "aUF-d1-5bR.title" = "Janela"; /* Class = "NSMenu"; title = "Font"; ObjectID = "aXa-aM-Jaq"; */ "aXa-aM-Jaq.title" = "Fonte"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "agt-UL-0e3"; */ "agt-UL-0e3.title" = "Usar Padrão"; /* Class = "NSMenuItem"; title = "Show Colors"; ObjectID = "bgn-CT-cEk"; */ "bgn-CT-cEk.title" = "Mostrar Cores"; /* Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu"; */ "bib-Uj-vzu.title" = "Arquivo"; /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "buJ-ug-pKt"; */ "buJ-ug-pKt.title" = "Usar Seleção Para Procurar"; /* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */ "c8a-y6-VQd.title" = "Transformações"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "cDB-IK-hbR"; */ "cDB-IK-hbR.title" = "Não Usar Nenhum"; /* Class = "NSMenuItem"; title = "Selection"; ObjectID = "cqv-fj-IhA"; */ "cqv-fj-IhA.title" = "Seleção"; /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */ "cwL-P1-jid.title" = "Links Inteligentes"; /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */ "d9M-CD-aMd.title" = "Tornar Minúsculo"; /* Class = "NSMenu"; title = "Text"; ObjectID = "d9c-me-L2H"; */ "d9c-me-L2H.title" = "Texto"; /* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */ "dMs-cI-mzQ.title" = "Arquivo"; /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ "dRJ-4n-Yzg.title" = "Desfazer"; /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ "gVA-U4-sdL.title" = "Colar"; /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */ "hQb-2v-fYv.title" = "Citações Inteligentes"; /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */ "hz2-CU-CR7.title" = "Checar Documento Agora"; /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ "hz9-B4-Xy5.title" = "Serviços"; /* Class = "NSMenuItem"; title = "Smaller"; ObjectID = "i1d-Er-qST"; */ "i1d-Er-qST.title" = "Diminuir"; /* Class = "NSMenu"; title = "Baseline"; ObjectID = "ijk-EB-dga"; */ "ijk-EB-dga.title" = "Linha de Base"; /* Class = "NSMenuItem"; title = "Kern"; ObjectID = "jBQ-r6-VK2"; */ "jBQ-r6-VK2.title" = "Kern"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "jFq-tB-4Kx"; */ "jFq-tB-4Kx.title" = "\tDireita Para Esquerda"; /* Class = "NSMenuItem"; title = "Format"; ObjectID = "jxT-CU-nIS"; */ "jxT-CU-nIS.title" = "Formatar"; /* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */ "kIP-vf-haE.title" = "Mostrar Barra Lateral"; /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */ "mK6-2p-4JG.title" = "Checar Gramática Com Ortografia"; /* Class = "NSMenuItem"; title = "Ligatures"; ObjectID = "o6e-r0-MWq"; */ "o6e-r0-MWq.title" = "Ligações"; /* Class = "NSMenu"; title = "Open Recent"; ObjectID = "oas-Oc-fiZ"; */ "oas-Oc-fiZ.title" = "Abrir Recente"; /* Class = "NSMenuItem"; title = "Loosen"; ObjectID = "ogc-rX-tC1"; */ "ogc-rX-tC1.title" = "Soltar"; /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ "pa3-QI-u2k.title" = "Deletar"; /* Class = "NSMenuItem"; title = "Save…"; ObjectID = "pxx-59-PXV"; */ "pxx-59-PXV.title" = "Salvar..."; /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "q09-fT-Sye"; */ "q09-fT-Sye.title" = "Encontrar Próximo"; /* Class = "NSMenuItem"; title = "Page Setup…"; ObjectID = "qIS-W8-SiK"; */ "qIS-W8-SiK.title" = "Configuração da Página..."; /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */ "rbD-Rh-wIN.title" = "Checar Ortografia Enquanto Digita"; /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */ "rgM-f4-ycn.title" = "Traços Inteligentes"; /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */ "snW-S8-Cw5.title" = "Mostrar Barra de Ferramentas"; /* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */ "tRr-pd-1PS.title" = "Detectores de Dados"; /* Class = "NSMenuItem"; title = "Open Recent"; ObjectID = "tXI-mr-wws"; */ "tXI-mr-wws.title" = "Abrir Recente"; /* Class = "NSMenu"; title = "Kern"; ObjectID = "tlD-Oa-oAM"; */ "tlD-Oa-oAM.title" = "Kern"; /* Class = "NSMenu"; title = "DevToys"; ObjectID = "uQy-DD-JDr"; */ "uQy-DD-JDr.title" = "DevToys"; /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ "uRl-iY-unG.title" = "Cortar"; /* Class = "NSMenuItem"; title = "Paste Style"; ObjectID = "vKC-jM-MkH"; */ "vKC-jM-MkH.title" = "Colar Estilo"; /* Class = "NSMenuItem"; title = "Show Ruler"; ObjectID = "vLm-3I-IUL"; */ "vLm-3I-IUL.title" = "Mostrar Régua"; /* Class = "NSMenuItem"; title = "Clear Menu"; ObjectID = "vNY-rz-j42"; */ "vNY-rz-j42.title" = "Limpar Menu"; /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */ "vmV-6d-7jI.title" = "Tornar Maiúsculo"; /* Class = "NSMenu"; title = "Ligatures"; ObjectID = "w0m-vy-SC9"; */ "w0m-vy-SC9.title" = "Ligações"; /* Class = "NSMenuItem"; title = "Align Right"; ObjectID = "wb2-vD-lq4"; */ "wb2-vD-lq4.title" = "Alinhar à Direita"; /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ "wpr-3q-Mcd.title" = "Ajuda"; /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ "x3v-GG-iWU.title" = "Copiar"; /* Class = "NSMenuItem"; title = "Use All"; ObjectID = "xQD-1f-W4t"; */ "xQD-1f-W4t.title" = "Usar Todos"; /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "xrE-MZ-jX0"; */ "xrE-MZ-jX0.title" = "Fala"; /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */ "z6F-FW-3nz.title" = "Mostrar Substituições"; ================================================ FILE: DevToys/DevToys/Resource/zh-Hans.lproj/Localizable.strings ================================================ /* Localizable.strings DevToys Created by wibus on 2022/02/13. */ // - General - "Configuration" = "配置"; "Format" = "格式"; "Pretty" = "美化"; "Minified" = "压缩"; "Encoded" = "编码"; "Decoded" = "解码"; "File" = "文件"; "Text" = "文本"; "Copy" = "复制"; "Paste" = "黏贴"; "Copied!" = "复制!"; "Pasted!" = "黏贴!"; "Open" = "打开"; "Export" = "导出"; "Import" = "导入"; "Input" = "输入"; "Output" = "输出"; "No selection" = "无选择"; "Drop Files Here" = "将文件拖入此处"; "Untitled Tool" = "未命名工具"; "Uppercase" = "大写"; "Hyphens" = "连字"; "Type" = "种类"; "Length" = "长度"; "Convert" = "转换"; "Information" = "信息"; "Clear" = "清空"; "Delete" = "删除"; // - Category - "category.home" = "首页"; "category.converters" = "转换类"; "category.encoders_decoders" = "编码 / 解码类"; "category.formatters" = "格式类"; "category.generators" = "生成类"; "category.text" = "文本类"; "category.graphic" = "图像类"; // - Tool - "tool.home.title" = "首页"; "tool.home.mintitle" = "首页"; "tool.home.description" = "在这里搜索所有工具."; "tool.jsonyaml.title" = "JSON <> Yaml 转换器"; "tool.jsonyaml.mintitle" = "JSON <> Yaml"; "tool.jsonyaml.description" = "将json与yaml相互转换"; "tool.numbase.title" = "数字类型转换器"; "tool.numbase.mintitle" = "数字类型转换"; "tool.numbase.description" = "将数字从一种类型转换为另一个类型"; "Format Number" = "格式化数字"; "Decimal" = "十进制"; "Hexdecimal" = "十六进制"; "Octal" = "八进制"; "Binary" = "二进制"; "tool.date.title" = "日期转换器"; "tool.date.mintitle" = "日期"; "tool.date.description" = "将日期从一种格式转换为另一种格式"; "Now" = "现在时间"; "Date" = "时间"; "Unix Time" = "Unix 时间"; "ISO 8601" = "ISO 8601"; "Calender" = "日历"; "tool.html.title" = "HTML 编码 / 解码"; "tool.html.mintitle" = "HTML"; "tool.html.description" = "将字符编码或解码为相应的HTML"; "tool.url.title" = "URL 编码 / 解码"; "tool.url.mintitle" = "URL"; "tool.url.description" = "将字符编码或解码为相应的URL"; "tool.base64.title" = "Base64 编码 / 解码"; "tool.base64.mintitle" = "Base64"; "tool.base64.description" = "编码或解码 Base64 数据"; "Source Type" = "数据来源"; "Text Source" = "字符串"; "File Source" = "文件"; "tool.jwt.title" = "JWT 编码 / 解码"; "tool.jwt.mintitle" = "JWT"; "tool.jwt.description" = "解码JWT头部的负载和签名"; "JWT Token" = "JWT 密钥"; "Header" = "头部"; "Payload" = "负载"; "tool.jsonformat.title" = "JSON 格式器"; "tool.jsonformat.mintitle" = "JSON"; "tool.jsonformat.description" = "缩进或压缩 json 数据"; "Indentation" = "Indentation"; "2 Spaces" = "2 Spaces"; "4 Spaces" = "4 Spaces"; "1 Tab" = "1 Tab"; "tool.xmlformat.title" = "XML 格式器"; "tool.xmlformat.mintitle" = "XML"; "tool.xmlformat.description" = "缩进或压缩 XML 数据"; "HTML Document" = "HTML 文档"; "XML Document" = "XML 文档"; "Document Type" = "文档类型"; "Auto Fix Document" = "自动修复"; "Pretty Document" = "美化"; "tool.hashgen.title" = "Hash 生成器"; "tool.hashgen.mintitle" = "Hash"; "tool.hashgen.description" = "从文本数据计算MD5, SHA1, SHA256和SHA 512哈希值"; "tool.uuidgen.title" = "UUID 生成器"; "tool.uuidgen.mintitle" = "UUID"; "tool.uuidgen.description" = "生成 UUIDs"; "Generate UUIDs" = "生成 UUIDs"; "tool.ligen.title" = "哑元文本生成器"; "tool.ligen.mintitle" = "哑元文本"; "tool.ligen.description" = "生成哑元文本占位符文本"; "Type of generating Lorem Ipsum" = "生成种类"; "Length of generating Lorem Ipsum" = "生成数量"; "Words" = "单词"; "Sentences" = "句子"; "Paragraphs" = "段落"; "tool.checksum.title" = "校验测试器"; "tool.checksum.mintitle" = "校验测试器"; "tool.checksum.description" = "生成或测试文件的校验和"; "Output Comparer" = "比较输出"; "Hash Algorithm" = "散列算法"; "Select which algorithm you want to use" = "选择要使用的算法"; "tool.checksum.title" = "校验测试器"; "tool.checksum.mintitle" = "校验测试器"; "tool.checksum.description" = "生成或测试文件的校验和"; "tool.textinspect.title" = "文本检查 & 大小写转换器"; "tool.textinspect.mintitle" = "检查 & 大小写 转换器"; "tool.textinspect.description" = "分析文本并将其转换为不同的大小写"; "Charactors" = "符号"; "Words" = "单词"; "Lines" = "行数"; "Bytes" = "字节数"; "tool.regex.title" = "正则表达式测试器"; "tool.regex.mintitle" = "正则表达式"; "tool.regex.description" = "测试正则表达式"; "Reguler expression" = "正则表达式"; "tool.hyphenremove.title" = "连字符号去除器"; "tool.hyphenremove.mintitle" = "连字符号"; "tool.hyphenremove.description" = "删除从PDF文本复制的连字符"; "tool.imageoptim.title" = "PNG / JPEG 压缩器"; "tool.imageoptim.mintitle" = "PNG / JPEG 压缩器"; "tool.imageoptim.description" = "无损的PNG和JPEG压缩器"; "Optimize Level" = "压缩级别"; "Low" = "低"; "Medium" = "中"; "High" = "高"; "Very High (Slow)" = "优 (慢)"; "tool.pdfgen.title" = "PDF 生成器"; "tool.pdfgen.mintitle" = "PDF 生成器"; "tool.pdfgen.description" = "从多个图像生成PDF"; "Images" = "图像"; "Size" = "大小"; "Generate PDF" = "生成 PDF"; "Page" = "页面"; "tool.imageconvert.title" = "Image 转换器"; "tool.imageconvert.mintitle" = "Image 转换器"; "tool.imageconvert.description" = "转换或调整图像大小"; "PNG Format" = "PNG 格式"; "JPEG Format" = "JPEG 格式"; "TIFF Format" = "TIFF 格式"; "GIF Format" = "GIF 格式"; "Scale to Fill" = "缩放使充满容器(未必保持长宽比例)"; "Scale to Fit" = "保持长宽比缩放"; "Image Format" = "图像格式"; "Resize" = "调整大小"; "Converted Images" = "转换图片"; "Scale" = "比例"; "Size" = "大小"; ================================================ FILE: DevToys/DevToys/Resource/zh-Hans.lproj/Main.strings ================================================ /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */ "1UK-8n-QPP.title" = "Customize Toolbar…"; /* Class = "NSMenuItem"; title = "DevToys"; ObjectID = "1Xt-HY-uBw"; */ "1Xt-HY-uBw.title" = "DevToys"; /* Class = "NSMenu"; title = "Find"; ObjectID = "1b7-l0-nxx"; */ "1b7-l0-nxx.title" = "Find"; /* Class = "NSMenuItem"; title = "Lower"; ObjectID = "1tx-W0-xDw"; */ "1tx-W0-xDw.title" = "Lower"; /* Class = "NSMenuItem"; title = "Raise"; ObjectID = "2h7-ER-AoG"; */ "2h7-ER-AoG.title" = "Raise"; /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */ "2oI-Rn-ZJC.title" = "Transformations"; /* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */ "3IN-sU-3Bg.title" = "Spelling"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "3Om-Ey-2VK"; */ "3Om-Ey-2VK.title" = "Use Default"; /* Class = "NSMenu"; title = "Speech"; ObjectID = "3rS-ZA-NoH"; */ "3rS-ZA-NoH.title" = "Speech"; /* Class = "NSMenuItem"; title = "Tighten"; ObjectID = "46P-cB-AYj"; */ "46P-cB-AYj.title" = "Tighten"; /* Class = "NSMenuItem"; title = "Find"; ObjectID = "4EN-yA-p0u"; */ "4EN-yA-p0u.title" = "Find"; /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */ "4J7-dP-txa.title" = "Enter Full Screen"; /* Class = "NSMenuItem"; title = "Quit DevToys"; ObjectID = "4sb-4s-VLi"; */ "4sb-4s-VLi.title" = "Quit DevToys"; /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ "5QF-Oa-p0T.title" = "Edit"; /* Class = "NSMenuItem"; title = "Copy Style"; ObjectID = "5Vv-lz-BsD"; */ "5Vv-lz-BsD.title" = "Copy Style"; /* Class = "NSMenuItem"; title = "About DevToys"; ObjectID = "5kV-Vb-QxS"; */ "5kV-Vb-QxS.title" = "About DevToys"; /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ "6dh-zS-Vam.title" = "Redo"; /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */ "78Y-hA-62v.title" = "Correct Spelling Automatically"; /* Class = "NSMenu"; title = "Writing Direction"; ObjectID = "8mr-sm-Yjd"; */ "8mr-sm-Yjd.title" = "Writing Direction"; /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */ "9ic-FL-obx.title" = "Substitutions"; /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */ "9yt-4B-nSM.title" = "Smart Copy/Paste"; /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ "AYu-sK-qS6.title" = "Main Menu"; /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ "BOF-NM-1cW.title" = "Preferences…"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "BgM-ve-c93"; */ "BgM-ve-c93.title" = "\tLeft to Right"; /* Class = "NSMenuItem"; title = "Save As…"; ObjectID = "Bw7-FT-i3A"; */ "Bw7-FT-i3A.title" = "Save As…"; /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */ "DVo-aG-piG.title" = "Close"; /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */ "Dv1-io-Yv7.title" = "Spelling and Grammar"; /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ "F2S-fz-NVQ.title" = "Help"; /* Class = "NSMenuItem"; title = "DevToys Help"; ObjectID = "FKE-Sm-Kum"; */ "FKE-Sm-Kum.title" = "DevToys Help"; /* Class = "NSMenuItem"; title = "Text"; ObjectID = "Fal-I4-PZk"; */ "Fal-I4-PZk.title" = "Text"; /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */ "FeM-D8-WVr.title" = "Substitutions"; /* Class = "NSMenuItem"; title = "Bold"; ObjectID = "GB9-OM-e27"; */ "GB9-OM-e27.title" = "Bold"; /* Class = "NSMenu"; title = "Format"; ObjectID = "GEO-Iw-cKr"; */ "GEO-Iw-cKr.title" = "Format"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "GUa-eO-cwY"; */ "GUa-eO-cwY.title" = "Use Default"; /* Class = "NSMenuItem"; title = "Font"; ObjectID = "Gi5-1S-RQB"; */ "Gi5-1S-RQB.title" = "Font"; /* Class = "NSMenuItem"; title = "Writing Direction"; ObjectID = "H1b-Si-o9J"; */ "H1b-Si-o9J.title" = "Writing Direction"; /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ "H8h-7b-M4v.title" = "View"; /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */ "HFQ-gK-NFA.title" = "Text Replacement"; /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */ "HFo-cy-zxI.title" = "Show Spelling and Grammar"; /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ "HyV-fh-RgO.title" = "View"; /* Class = "NSMenuItem"; title = "Subscript"; ObjectID = "I0S-gh-46l"; */ "I0S-gh-46l.title" = "Subscript"; /* Class = "NSMenuItem"; title = "Open…"; ObjectID = "IAo-SY-fd9"; */ "IAo-SY-fd9.title" = "Open…"; /* Class = "NSWindow"; title = "DevToys"; ObjectID = "IQv-IB-iLA"; */ "IQv-IB-iLA.title" = "DevToys"; /* Class = "NSMenuItem"; title = "Justify"; ObjectID = "J5U-5w-g23"; */ "J5U-5w-g23.title" = "Justify"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "J7y-lM-qPV"; */ "J7y-lM-qPV.title" = "Use None"; /* Class = "NSMenuItem"; title = "Revert to Saved"; ObjectID = "KaW-ft-85H"; */ "KaW-ft-85H.title" = "Revert to Saved"; /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ "Kd2-mp-pUS.title" = "Show All"; /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ "LE2-aR-0XJ.title" = "Bring All to Front"; /* Class = "NSMenuItem"; title = "Paste Ruler"; ObjectID = "LVM-kO-fVI"; */ "LVM-kO-fVI.title" = "Paste Ruler"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "Lbh-J2-qVU"; */ "Lbh-J2-qVU.title" = "\tLeft to Right"; /* Class = "NSMenuItem"; title = "Copy Ruler"; ObjectID = "MkV-Pr-PK5"; */ "MkV-Pr-PK5.title" = "Copy Ruler"; /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ "NMo-om-nkz.title" = "Services"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "Nop-cj-93Q"; */ "Nop-cj-93Q.title" = "\tDefault"; /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ "OY7-WF-poV.title" = "Minimize"; /* Class = "NSMenuItem"; title = "Baseline"; ObjectID = "OaQ-X3-Vso"; */ "OaQ-X3-Vso.title" = "Baseline"; /* Class = "NSMenuItem"; title = "Hide DevToys"; ObjectID = "Olw-nP-bQN"; */ "Olw-nP-bQN.title" = "Hide DevToys"; /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "OwM-mh-QMV"; */ "OwM-mh-QMV.title" = "Find Previous"; /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "Oyz-dy-DGm"; */ "Oyz-dy-DGm.title" = "Stop Speaking"; /* Class = "NSMenuItem"; title = "Bigger"; ObjectID = "Ptp-SP-VEL"; */ "Ptp-SP-VEL.title" = "Bigger"; /* Class = "NSMenuItem"; title = "Show Fonts"; ObjectID = "Q5e-8K-NDq"; */ "Q5e-8K-NDq.title" = "Show Fonts"; /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ "R4o-n2-Eq4.title" = "Zoom"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "RB4-Sm-HuC"; */ "RB4-Sm-HuC.title" = "\tRight to Left"; /* Class = "NSMenuItem"; title = "Superscript"; ObjectID = "Rqc-34-cIF"; */ "Rqc-34-cIF.title" = "Superscript"; /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ "Ruw-6m-B2m.title" = "Select All"; /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "S0p-oC-mLd"; */ "S0p-oC-mLd.title" = "Jump to Selection"; /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ "Td7-aD-5lo.title" = "Window"; /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */ "UEZ-Bs-lqG.title" = "Capitalize"; /* Class = "NSMenuItem"; title = "Center"; ObjectID = "VIY-Ag-zcb"; */ "VIY-Ag-zcb.title" = "Center"; /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ "Vdr-fp-XzO.title" = "Hide Others"; /* Class = "NSMenuItem"; title = "Italic"; ObjectID = "Vjx-xi-njq"; */ "Vjx-xi-njq.title" = "Italic"; /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ "W48-6f-4Dl.title" = "Edit"; /* Class = "NSMenuItem"; title = "Underline"; ObjectID = "WRG-CD-K1S"; */ "WRG-CD-K1S.title" = "Underline"; /* Class = "NSMenuItem"; title = "New"; ObjectID = "Was-JA-tGl"; */ "Was-JA-tGl.title" = "New"; /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ "WeT-3V-zwk.title" = "Paste and Match Style"; /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "Xz5-n4-O0W"; */ "Xz5-n4-O0W.title" = "Find…"; /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "YEy-JH-Tfz"; */ "YEy-JH-Tfz.title" = "Find and Replace…"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "YGs-j5-SAR"; */ "YGs-j5-SAR.title" = "\tDefault"; /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "Ynk-f8-cLZ"; */ "Ynk-f8-cLZ.title" = "Start Speaking"; /* Class = "NSMenuItem"; title = "Align Left"; ObjectID = "ZM1-6Q-yy1"; */ "ZM1-6Q-yy1.title" = "Align Left"; /* Class = "NSMenuItem"; title = "Paragraph"; ObjectID = "ZvO-Gk-QUH"; */ "ZvO-Gk-QUH.title" = "Paragraph"; /* Class = "NSMenuItem"; title = "Print…"; ObjectID = "aTl-1u-JFS"; */ "aTl-1u-JFS.title" = "Print…"; /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ "aUF-d1-5bR.title" = "Window"; /* Class = "NSMenu"; title = "Font"; ObjectID = "aXa-aM-Jaq"; */ "aXa-aM-Jaq.title" = "Font"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "agt-UL-0e3"; */ "agt-UL-0e3.title" = "Use Default"; /* Class = "NSMenuItem"; title = "Show Colors"; ObjectID = "bgn-CT-cEk"; */ "bgn-CT-cEk.title" = "Show Colors"; /* Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu"; */ "bib-Uj-vzu.title" = "File"; /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "buJ-ug-pKt"; */ "buJ-ug-pKt.title" = "Use Selection for Find"; /* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */ "c8a-y6-VQd.title" = "Transformations"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "cDB-IK-hbR"; */ "cDB-IK-hbR.title" = "Use None"; /* Class = "NSMenuItem"; title = "Selection"; ObjectID = "cqv-fj-IhA"; */ "cqv-fj-IhA.title" = "Selection"; /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */ "cwL-P1-jid.title" = "Smart Links"; /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */ "d9M-CD-aMd.title" = "Make Lower Case"; /* Class = "NSMenu"; title = "Text"; ObjectID = "d9c-me-L2H"; */ "d9c-me-L2H.title" = "Text"; /* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */ "dMs-cI-mzQ.title" = "File"; /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ "dRJ-4n-Yzg.title" = "Undo"; /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ "gVA-U4-sdL.title" = "Paste"; /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */ "hQb-2v-fYv.title" = "Smart Quotes"; /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */ "hz2-CU-CR7.title" = "Check Document Now"; /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ "hz9-B4-Xy5.title" = "Services"; /* Class = "NSMenuItem"; title = "Smaller"; ObjectID = "i1d-Er-qST"; */ "i1d-Er-qST.title" = "Smaller"; /* Class = "NSMenu"; title = "Baseline"; ObjectID = "ijk-EB-dga"; */ "ijk-EB-dga.title" = "Baseline"; /* Class = "NSMenuItem"; title = "Kern"; ObjectID = "jBQ-r6-VK2"; */ "jBQ-r6-VK2.title" = "Kern"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "jFq-tB-4Kx"; */ "jFq-tB-4Kx.title" = "\tRight to Left"; /* Class = "NSMenuItem"; title = "Format"; ObjectID = "jxT-CU-nIS"; */ "jxT-CU-nIS.title" = "Format"; /* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */ "kIP-vf-haE.title" = "Show Sidebar"; /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */ "mK6-2p-4JG.title" = "Check Grammar With Spelling"; /* Class = "NSMenuItem"; title = "Ligatures"; ObjectID = "o6e-r0-MWq"; */ "o6e-r0-MWq.title" = "Ligatures"; /* Class = "NSMenu"; title = "Open Recent"; ObjectID = "oas-Oc-fiZ"; */ "oas-Oc-fiZ.title" = "Open Recent"; /* Class = "NSMenuItem"; title = "Loosen"; ObjectID = "ogc-rX-tC1"; */ "ogc-rX-tC1.title" = "Loosen"; /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ "pa3-QI-u2k.title" = "Delete"; /* Class = "NSMenuItem"; title = "Save…"; ObjectID = "pxx-59-PXV"; */ "pxx-59-PXV.title" = "Save…"; /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "q09-fT-Sye"; */ "q09-fT-Sye.title" = "Find Next"; /* Class = "NSMenuItem"; title = "Page Setup…"; ObjectID = "qIS-W8-SiK"; */ "qIS-W8-SiK.title" = "Page Setup…"; /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */ "rbD-Rh-wIN.title" = "Check Spelling While Typing"; /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */ "rgM-f4-ycn.title" = "Smart Dashes"; /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */ "snW-S8-Cw5.title" = "Show Toolbar"; /* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */ "tRr-pd-1PS.title" = "Data Detectors"; /* Class = "NSMenuItem"; title = "Open Recent"; ObjectID = "tXI-mr-wws"; */ "tXI-mr-wws.title" = "Open Recent"; /* Class = "NSMenu"; title = "Kern"; ObjectID = "tlD-Oa-oAM"; */ "tlD-Oa-oAM.title" = "Kern"; /* Class = "NSMenu"; title = "DevToys"; ObjectID = "uQy-DD-JDr"; */ "uQy-DD-JDr.title" = "DevToys"; /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ "uRl-iY-unG.title" = "Cut"; /* Class = "NSMenuItem"; title = "Paste Style"; ObjectID = "vKC-jM-MkH"; */ "vKC-jM-MkH.title" = "Paste Style"; /* Class = "NSMenuItem"; title = "Show Ruler"; ObjectID = "vLm-3I-IUL"; */ "vLm-3I-IUL.title" = "Show Ruler"; /* Class = "NSMenuItem"; title = "Clear Menu"; ObjectID = "vNY-rz-j42"; */ "vNY-rz-j42.title" = "Clear Menu"; /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */ "vmV-6d-7jI.title" = "Make Upper Case"; /* Class = "NSMenu"; title = "Ligatures"; ObjectID = "w0m-vy-SC9"; */ "w0m-vy-SC9.title" = "Ligatures"; /* Class = "NSMenuItem"; title = "Align Right"; ObjectID = "wb2-vD-lq4"; */ "wb2-vD-lq4.title" = "Align Right"; /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ "wpr-3q-Mcd.title" = "Help"; /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ "x3v-GG-iWU.title" = "Copy"; /* Class = "NSMenuItem"; title = "Use All"; ObjectID = "xQD-1f-W4t"; */ "xQD-1f-W4t.title" = "Use All"; /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "xrE-MZ-jX0"; */ "xrE-MZ-jX0.title" = "Speech"; /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */ "z6F-FW-3nz.title" = "Show Substitutions"; ================================================ FILE: DevToys/DevToys/Sidebar/SearchCell.swift ================================================ // // SidebarSearchView+.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil final class SidebarSearchCellController: NSViewController { private let cell = SidebarSearchCell() override func loadView() { self.view = cell } override func chainObjectDidLoad() { self.appModel.$searchQuery .sink{[unowned self] in self.cell.searchView.stringValue = $0 }.store(in: &objectBag) self.cell.searchView.changeStringPublisher.merge(with: self.cell.searchView.endEditingStringPublisher) .sink{[unowned self] in self.appModel.searchQuery = $0 }.store(in: &objectBag) } } final class SidebarSearchCell: NSLoadView { static let height: CGFloat = 48 let searchView = SidebarSearchField() override func onAwake() { self.addSubview(searchView) self.searchView.snp.makeConstraints{ make in make.height.equalTo(29) make.centerY.equalToSuperview() make.right.left.equalToSuperview() } } } final class SidebarSearchField: NSSearchField { override var focusRingMaskBounds: NSRect { bounds } override func drawFocusRingMask() { NSBezierPath(roundedRect: bounds.slimmed(by: 1), xRadius: 4, yRadius: 4).fill() } } ================================================ FILE: DevToys/DevToys/Sidebar/SidebarView+.swift ================================================ // // ToolmenuViewController.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil final class SidebarViewController: NSViewController { private let searchViewController = SidebarSearchCellController() private let outlineView = SidebarOutlineView.list() private let scrollView = NSScrollView() @RestorableState("toolmenu.initial") private var isInitial = true @objc func onClick(_ outlineView: NSOutlineView) { self.onSelect(row: outlineView.clickedRow) } private func onSelect(row: Int) { guard let tool = outlineView.item(atRow: row) as? Tool else { return } self.appModel.tool = tool } override func loadView() { self.view = scrollView self.scrollView.documentView = outlineView self.scrollView.drawsBackground = false self.outlineView.setTarget(self, action: #selector(onClick)) self.outlineView.outlineTableColumn = self.outlineView.tableColumns[0] self.outlineView.selectionHighlightStyle = .sourceList self.outlineView.floatsGroupRows = false self.addChild(searchViewController) } override func chainObjectDidLoad() { // Datasource uses chainObject, call it in `chainObjectDidLoad` self.outlineView.delegate = self self.outlineView.dataSource = self self.outlineView.autosaveExpandedItems = true self.outlineView.autosaveName = "sidebar" if self.isInitial { self.isInitial = false self.outlineView.expandItem(nil, expandChildren: true) } } } final private class SidebarOutlineView: NSOutlineView { override func mouseDown(with event: NSEvent) { super.mouseDown(with: event) let row = row(at: event.location(in: self)) if row >= 0, let cell = self.view(atColumn: 0, row: row, makeIfNecessary: false) as? SidebarSearchCell, window?.firstResponder !== cell.searchView { window?.makeFirstResponder(cell.searchView) } } } extension SidebarViewController: NSOutlineViewDataSource { func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool { item is ToolCategory } func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { item is ToolCategory } func outlineView(_ outlineView: NSOutlineView, shouldSelectItem item: Any) -> Bool { item is Tool } func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { if item == nil { if index == 0 { return () } else { return appModel.toolManager.flattenRootItems()[index-1] } } guard let category = item as? ToolCategory else { return () } return appModel.toolManager.toolsForCategory(category)[index] } func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { if item == nil { return appModel.toolManager.flattenRootItems().count + 1 } guard let category = item as? ToolCategory else { return 0 } return appModel.toolManager.toolsForCategory(category).count } func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat { if item is ToolCategory { return ToolCategoryCell.height } else if item is Tool { return ToolMenuCell.height } else { return SidebarSearchCell.height } } func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { if let category = item as? ToolCategory { let cell = ToolCategoryCell() cell.title = category.name return cell } else if let tool = item as? Tool { let cell = ToolMenuCell() cell.title = tool.sidebarTitle cell.icon = tool.icon return cell } else if item is Void { return searchViewController.view } return nil } func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? { if let category = item as? ToolCategory { return category.identifier } else if let tool = item as? Tool { return tool.identifier } return nil } func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? { guard let identifier = object as? String else { return nil } if let category = appModel.toolManager.categoryForIdentifier(identifier) { return category } if let tool = appModel.toolManager.toolForIdentifier(identifier) { return tool } return nil } } extension SidebarViewController: NSOutlineViewDelegate { func outlineViewSelectionDidChange(_ notification: Notification) { self.onSelect(row: outlineView.selectedRow) } } ================================================ FILE: DevToys/DevToys/Sidebar/ToolCategoryCell.swift ================================================ // // ToolCategoryCell.swift // DevToys // // Created by yuki on 2022/02/16. // import CoreUtil final class ToolCategoryCell: NSLoadView { static let height: CGFloat = 21 var title: String { get { titleLabel.stringValue } set { titleLabel.stringValue = newValue } } private let titleLabel = NSTextField(labelWithString: "Title") override func onAwake() { self.addSubview(titleLabel) self.titleLabel.font = .systemFont(ofSize: 11, weight: .semibold) self.titleLabel.textColor = .tertiaryLabelColor self.titleLabel.snp.makeConstraints{ make in make.left.right.equalToSuperview().inset(8) make.centerY.equalToSuperview() } } } ================================================ FILE: DevToys/DevToys/Sidebar/ToolMenuCell.swift ================================================ // // ToolmenuCell.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil final class ToolMenuCell: NSLoadView { static let height: CGFloat = 28 var title: String { get { titleLabel.stringValue } set { titleLabel.stringValue = newValue } } var icon: NSImage? { get { iconView.image } set { iconView.image = newValue } } private let titleLabel = NSTextField(labelWithString: "Title") private let iconView = NSImageView() override func onAwake() { self.snp.makeConstraints{ make in make.height.equalTo(Self.height) } self.addSubview(iconView) self.iconView.snp.makeConstraints{ make in make.size.equalTo(20) make.left.equalTo(8) make.centerY.equalToSuperview() } self.addSubview(titleLabel) self.titleLabel.lineBreakMode = .byTruncatingTail self.titleLabel.font = .systemFont(ofSize: 12) self.titleLabel.snp.makeConstraints{ make in make.left.equalTo(self.iconView.snp.right).offset(8) make.right.equalToSuperview().inset(4) make.centerY.equalToSuperview() } } } ================================================ FILE: DevToys/DevToys/Sidebar/View/Separator.swift ================================================ // // Separator.swift // DevToys // // Created by yuki on 2022/01/29. // import CoreUtil final class SeparatorView: NSLoadView { private let separatorLayer = CALayer.animationDisabled() public override func layout() { super.layout() self.separatorLayer.frame = bounds } public override func updateLayer() { self.separatorLayer.backgroundColor = NSColor.textColor.withAlphaComponent(0.1).cgColor } override public func onAwake() { self.wantsLayer = true self.layer?.addSublayer(separatorLayer) self.snp.makeConstraints{ make in make.height.equalTo(1) } } } ================================================ FILE: DevToys/DevToys.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 52; objects = { /* Begin PBXBuildFile section */ 6B42318827AD1BC0002D135A /* HyphenationRemoverView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B42318727AD1BC0002D135A /* HyphenationRemoverView+.swift */; }; B608540B27A66694003BF243 /* SectionButton+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B608540A27A66694003BF243 /* SectionButton+.swift */; }; B608541227A66A90003BF243 /* JSONYamlConverterView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B608541127A66A90003BF243 /* JSONYamlConverterView+.swift */; }; B608541527A66CC9003BF243 /* CoreUtil.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B64B1F5E27A4FC1900AC2601 /* CoreUtil.framework */; }; B608541627A66CC9003BF243 /* CoreUtil.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B64B1F5E27A4FC1900AC2601 /* CoreUtil.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B608541B27A672E3003BF243 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = B608541A27A672E3003BF243 /* Yams */; }; B608541E27A67B48003BF243 /* NumberBaseConverterView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B608541D27A67B48003BF243 /* NumberBaseConverterView+.swift */; }; B608542027A67E4D003BF243 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B608541F27A67E4D003BF243 /* TextField.swift */; }; B6113ECD27C4B96C00ACC6E8 /* EmptyImageTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6113ECC27C4B96C00ACC6E8 /* EmptyImageTableView.swift */; }; B6113ECF27C4BF2300ACC6E8 /* FileConflictAvoider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6113ECE27C4BF2300ACC6E8 /* FileConflictAvoider.swift */; }; B6113F0127C4BF7700ACC6E8 /* Identifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6113F0027C4BF7700ACC6E8 /* Identifier.swift */; }; B6113F0927C4C6C900ACC6E8 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = B6113F0827C4C6C900ACC6E8 /* Sparkle */; }; B61157C027C8C3F9004D77A5 /* JSONSearchView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61157BF27C8C3F9004D77A5 /* JSONSearchView+.swift */; }; B61157C327C8C78A004D77A5 /* JSONNormalSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61157C227C8C78A004D77A5 /* JSONNormalSearchView.swift */; }; B61157C527C8D77A004D77A5 /* DragImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61157C427C8D77A004D77A5 /* DragImageView.swift */; }; B61796C527BCB3090054660E /* ToolCategoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61796C427BCB3090054660E /* ToolCategoryCell.swift */; }; B61796C727BCB6F00054660E /* SettingView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61796C627BCB6F00054660E /* SettingView+.swift */; }; B61796C927BCB84A0054660E /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61796C827BCB84A0054660E /* Settings.swift */; }; B62013B427A9070D00AF5386 /* UUIDGeneratorView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62013B327A9070D00AF5386 /* UUIDGeneratorView+.swift */; }; B62013B627A90B8900AF5386 /* NumberField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62013B527A90B8900AF5386 /* NumberField.swift */; }; B620141C27A917E000AF5386 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = B620141B27A917E000AF5386 /* Button.swift */; }; B620141E27A91F6700AF5386 /* LoremIpsumGeneratorView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B620141D27A91F6700AF5386 /* LoremIpsumGeneratorView+.swift */; }; B620142127A9271500AF5386 /* TextInspectorView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B620142027A9271500AF5386 /* TextInspectorView+.swift */; }; B620142327A929B400AF5386 /* TagCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B620142227A929B400AF5386 /* TagCloudView.swift */; }; B627294F27BFB7680034D70C /* WebpImageExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B627294E27BFB7680034D70C /* WebpImageExporter.swift */; }; B627295227BFB8CB0034D70C /* cwebp in Resources */ = {isa = PBXBuildFile; fileRef = B627295127BFB8CB0034D70C /* cwebp */; }; B627295727BFCCC40034D70C /* HeicImageExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B627295627BFCCC40034D70C /* HeicImageExporter.swift */; }; B63A034B27C2130A009FD2AD /* GifConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63A034A27C2130A009FD2AD /* GifConverter.swift */; }; B63A034D27C21A99009FD2AD /* ffmpeg in Resources */ = {isa = PBXBuildFile; fileRef = B63A034C27C21A99009FD2AD /* ffmpeg */; }; B63A035027C24BA0009FD2AD /* FFProgressReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63A034F27C24BA0009FD2AD /* FFProgressReport.swift */; }; B63A035227C25B29009FD2AD /* FFTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63A035127C25B29009FD2AD /* FFTime.swift */; }; B64844FF27B767EC004FE02B /* ToolManager+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64844FE27B767EC004FE02B /* ToolManager+.swift */; }; B64AA45027AD0D3900EC436D /* DateConverterView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64AA44F27AD0D3900EC436D /* DateConverterView+.swift */; }; B64B1E7427A4F5E200AC2601 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1E7327A4F5E200AC2601 /* AppDelegate.swift */; }; B64B1E7627A4F5E200AC2601 /* AppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1E7527A4F5E200AC2601 /* AppViewController.swift */; }; B64B1E7827A4F5E300AC2601 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B64B1E7727A4F5E300AC2601 /* Assets.xcassets */; }; B64B1E7B27A4F5E300AC2601 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B64B1E7927A4F5E300AC2601 /* Main.storyboard */; }; B64B1F7227A4FDC800AC2601 /* AppModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F7127A4FDC800AC2601 /* AppModel.swift */; }; B64B1F7727A4FF8B00AC2601 /* SidebarView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B1F7627A4FF8B00AC2601 /* SidebarView+.swift */; }; B64B201627A5100800AC2601 /* Separator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B201527A5100800AC2601 /* Separator.swift */; }; B64B201D27A5219400AC2601 /* SearchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B201C27A5219400AC2601 /* SearchCell.swift */; }; B64B202027A523A200AC2601 /* ToolMenuCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B201F27A523A200AC2601 /* ToolMenuCell.swift */; }; B64B202327A52A2D00AC2601 /* R.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B202227A52A2D00AC2601 /* R.swift */; }; B64B209827A532DF00AC2601 /* AppWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64B209727A532DF00AC2601 /* AppWindowController.swift */; }; B65DB78027AC0EB400146A3C /* RegexTesterView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65DB77F27AC0EB400146A3C /* RegexTesterView+.swift */; }; B66536CE27C9BA4000CA1CEC /* Slugify.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66536CD27C9BA4000CA1CEC /* Slugify.swift */; }; B66536D027C9C56600CA1CEC /* SQLFormatterView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66536CF27C9C56600CA1CEC /* SQLFormatterView+.swift */; }; B66850EE27A64D3200A3FE01 /* Page.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66850ED27A64D3200A3FE01 /* Page.swift */; }; B66850F227A65FC200A3FE01 /* SwiftJSONFormatter in Frameworks */ = {isa = PBXBuildFile; productRef = B66850F127A65FC200A3FE01 /* SwiftJSONFormatter */; }; B66BE8BC27C49AD4003B5ED0 /* AudioFileScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66BE8BB27C49AD4003B5ED0 /* AudioFileScanner.swift */; }; B672CFA027AAB8DD00391A5D /* FileDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = B672CF9F27AAB8DD00391A5D /* FileDrop.swift */; }; B673A90227AD0E78005512AB /* DatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B673A90127AD0E78005512AB /* DatePicker.swift */; }; B6789EBA27CA43E200D8B58C /* IconGeneratorView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6789EB927CA43E100D8B58C /* IconGeneratorView+.swift */; }; B6789EBC27CA48A300D8B58C /* IosIconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6789EBB27CA48A300D8B58C /* IosIconGenerator.swift */; }; B6789EDF27CA5F5D00D8B58C /* IconsetGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6789EDE27CA5F5D00D8B58C /* IconsetGenerator.swift */; }; B6789EE127CA5F6900D8B58C /* IcnsGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6789EE027CA5F6900D8B58C /* IcnsGenerator.swift */; }; B6789EE327CA5F7A00D8B58C /* IconFolderGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6789EE227CA5F7A00D8B58C /* IconFolderGenerator.swift */; }; B6789EE527CA5F9500D8B58C /* IconGenerator+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6789EE427CA5F9500D8B58C /* IconGenerator+Model.swift */; }; B6789EE727CB334D00D8B58C /* AndroidIconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6789EE627CB334D00D8B58C /* AndroidIconGenerator.swift */; }; B67A4C0827C0BA00009277FB /* ColorPickerHandleLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C0227C0B9FF009277FB /* ColorPickerHandleLayer.swift */; }; B67A4C0927C0BA00009277FB /* OpacityBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C0327C0B9FF009277FB /* OpacityBarView.swift */; }; B67A4C0A27C0BA00009277FB /* HueBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C0427C0B9FF009277FB /* HueBarView.swift */; }; B67A4C0B27C0BA00009277FB /* ColorPickerView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C0527C0B9FF009277FB /* ColorPickerView+.swift */; }; B67A4C0C27C0BA00009277FB /* ColorBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C0627C0B9FF009277FB /* ColorBoxView.swift */; }; B67A4C0D27C0BA00009277FB /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C0727C0B9FF009277FB /* Color.swift */; }; B67A4C4D27C0EA84009277FB /* ACPixelPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C3F27C0EA84009277FB /* ACPixelPicker.swift */; }; B67A4C4E27C0EA84009277FB /* ACOverlayPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C4027C0EA84009277FB /* ACOverlayPreview.swift */; }; B67A4C4F27C0EA84009277FB /* Ex+NSBezierPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C4227C0EA84009277FB /* Ex+NSBezierPath.swift */; }; B67A4C5027C0EA84009277FB /* Util+PixelPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C4427C0EA84009277FB /* Util+PixelPicker.swift */; }; B67A4C5127C0EA84009277FB /* ShowAndHideCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C4527C0EA84009277FB /* ShowAndHideCursor.swift */; }; B67A4C5227C0EA84009277FB /* ShowAndHideCursor.m in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C4627C0EA84009277FB /* ShowAndHideCursor.m */; }; B67A4C5327C0EA84009277FB /* Ex+NSColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C4727C0EA84009277FB /* Ex+NSColor.swift */; }; B67A4C5427C0EA84009277FB /* Ex+CGImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C4827C0EA84009277FB /* Ex+CGImage.swift */; }; B67A4C5527C0EA84009277FB /* ACOverlayPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C4927C0EA84009277FB /* ACOverlayPanel.swift */; }; B67A4C5627C0EA84009277FB /* ACOverlayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C4A27C0EA84009277FB /* ACOverlayController.swift */; }; B67A4C5727C0EA84009277FB /* ACOverlayController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B67A4C4B27C0EA84009277FB /* ACOverlayController.xib */; }; B67A4C5827C0EA84009277FB /* ACOverlayWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4C4C27C0EA84009277FB /* ACOverlayWrapper.swift */; }; B67A4CBE27C0EEEF009277FB /* ColorSampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4CBD27C0EEEF009277FB /* ColorSampleView.swift */; }; B67A4CC027C0F4BA009277FB /* BoxHSBColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4CBF27C0F4BA009277FB /* BoxHSBColorPicker.swift */; }; B67A4CC327C0F4D4009277FB /* CircleHueBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4CC227C0F4D4009277FB /* CircleHueBarView.swift */; }; B67A4CC527C0F50D009277FB /* R+ColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4CC427C0F50D009277FB /* R+ColorPicker.swift */; }; B67A4CC727C0F546009277FB /* CircleBoxHSBColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4CC627C0F546009277FB /* CircleBoxHSBColorPicker.swift */; }; B67A4CC927C10493009277FB /* SaturationBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4CC827C10493009277FB /* SaturationBarView.swift */; }; B67A4CCB27C10548009277FB /* BrightnessBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4CCA27C10548009277FB /* BrightnessBarView.swift */; }; B67A4CCD27C12C09009277FB /* CircleBarsHSBColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67A4CCC27C12C09009277FB /* CircleBarsHSBColorPicker.swift */; }; B680A79527A68B35007CB707 /* HTMLDecoderView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B680A79427A68B35007CB707 /* HTMLDecoderView+.swift */; }; B680A79827A69268007CB707 /* HTMLEntities in Frameworks */ = {isa = PBXBuildFile; productRef = B680A79727A69268007CB707 /* HTMLEntities */; }; B680A79A27A69324007CB707 /* URLDecoderView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B680A79927A69324007CB707 /* URLDecoderView+.swift */; }; B680A79C27A6947D007CB707 /* Base64DecoderView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B680A79B27A6947D007CB707 /* Base64DecoderView+.swift */; }; B680A83327A8BF7D007CB707 /* TextViewSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B680A83227A8BF7D007CB707 /* TextViewSection.swift */; }; B680A86827A8D4D1007CB707 /* TextFieldSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B680A86727A8D4D1007CB707 /* TextFieldSection.swift */; }; B680A89A27A8D9DD007CB707 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = B680A89927A8D9DD007CB707 /* Toast.swift */; }; B680A8A727A8DD5D007CB707 /* JWTDecoderView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B680A8A627A8DD5D007CB707 /* JWTDecoderView+.swift */; }; B680A8AA27A8E106007CB707 /* HashGeneratorView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B680A8A927A8E106007CB707 /* HashGeneratorView+.swift */; }; B680A8AD27A8E31C007CB707 /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B680A8AC27A8E31C007CB707 /* CryptoSwift */; }; B684EA2627C5EFDB0014802F /* DiffMatchPatch in Frameworks */ = {isa = PBXBuildFile; productRef = B684EA2527C5EFDB0014802F /* DiffMatchPatch */; }; B684EA2827C5EFE90014802F /* TextDiffView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B684EA2727C5EFE90014802F /* TextDiffView+.swift */; }; B684EA2A27C609620014802F /* QRCodeGeneratorView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B684EA2927C609620014802F /* QRCodeGeneratorView+.swift */; }; B69980D427CCCF0A0063F63D /* android_mask.png in Resources */ = {isa = PBXBuildFile; fileRef = B69980D327CCCF0A0063F63D /* android_mask.png */; }; B69980D727CCE09A0063F63D /* IcoGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69980D627CCE09A0063F63D /* IcoGenerator.swift */; }; B69980D927CCEB320063F63D /* PngIconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69980D827CCEB320063F63D /* PngIconGenerator.swift */; }; B69F0E8327CBC2100032F96A /* folder_back_64_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E7C27CBC20F0032F96A /* folder_back_64_bs.png */; }; B69F0E8427CBC2100032F96A /* folder_back_128_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E7D27CBC20F0032F96A /* folder_back_128_bs.png */; }; B69F0E8527CBC2100032F96A /* folder_back_256_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E7E27CBC20F0032F96A /* folder_back_256_bs.png */; }; B69F0E8627CBC2100032F96A /* folder_back_1024_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E7F27CBC20F0032F96A /* folder_back_1024_bs.png */; }; B69F0E8727CBC2100032F96A /* folder_back_32_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8027CBC20F0032F96A /* folder_back_32_bs.png */; }; B69F0E8827CBC2100032F96A /* folder_back_16_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8127CBC20F0032F96A /* folder_back_16_bs.png */; }; B69F0E8927CBC2100032F96A /* folder_back_512_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8227CBC20F0032F96A /* folder_back_512_bs.png */; }; B69F0E9127CBC2340032F96A /* folder_mask2_1024_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8B27CBC2340032F96A /* folder_mask2_1024_bs.png */; }; B69F0E9227CBC2340032F96A /* folder_mask2_512_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8C27CBC2340032F96A /* folder_mask2_512_bs.png */; }; B69F0E9327CBC2340032F96A /* folder_mask2_16_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8D27CBC2340032F96A /* folder_mask2_16_bs.png */; }; B69F0E9427CBC2340032F96A /* folder_mask2_32_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8E27CBC2340032F96A /* folder_mask2_32_bs.png */; }; B69F0E9527CBC2340032F96A /* folder_mask2_64_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8F27CBC2340032F96A /* folder_mask2_64_bs.png */; }; B69F0E9627CBC2340032F96A /* folder_mask2_128_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E9027CBC2340032F96A /* folder_mask2_128_bs.png */; }; B69F0E9827CBC2800032F96A /* folder_mask2_256_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E9727CBC2800032F96A /* folder_mask2_256_bs.png */; }; B69F0E9C27CBC7550032F96A /* folder_top_1024.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E9A27CBC7550032F96A /* folder_top_1024.png */; }; B69F0E9D27CBC7550032F96A /* folder_top_512.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E9B27CBC7550032F96A /* folder_top_512.png */; }; B69F0E9F27CC76A50032F96A /* IconImageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69F0E9E27CC76A50032F96A /* IconImageManager.swift */; }; B69F0EA627CC7CF40032F96A /* watermark_mask_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0EA427CC7CF40032F96A /* watermark_mask_bs.png */; }; B69F0EA727CC7CF40032F96A /* watermark_mask_dark_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0EA527CC7CF40032F96A /* watermark_mask_dark_bs.png */; }; B6A4F2A227CD05F000BBDE7E /* QRCodeReaderView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A4F2A127CD05F000BBDE7E /* QRCodeReaderView+.swift */; }; B6A4F2A427CD078100BBDE7E /* ImageDropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A4F2A327CD078100BBDE7E /* ImageDropView.swift */; }; B6A93D3227CBBC97003A6D7F /* IconTemplete+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A93D2F27CBBC97003A6D7F /* IconTemplete+.swift */; }; B6A93D3327CBBC97003A6D7F /* IconSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A93D3027CBBC97003A6D7F /* IconSet.swift */; }; B6A93D3427CBBC97003A6D7F /* IconTemplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A93D3127CBBC97003A6D7F /* IconTemplete.swift */; }; B6AC273027AA1373000FD713 /* optipng in Resources */ = {isa = PBXBuildFile; fileRef = B6AC272F27AA1373000FD713 /* optipng */; }; B6AC273227AA2BF6000FD713 /* ImageOptimizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AC273127AA2BF6000FD713 /* ImageOptimizer.swift */; }; B6AC276527AA3D61000FD713 /* jpegoptim in Resources */ = {isa = PBXBuildFile; fileRef = B6AC276427AA3D61000FD713 /* jpegoptim */; }; B6AC276A27AA535A000FD713 /* PDFGeneratorView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AC276927AA535A000FD713 /* PDFGeneratorView+.swift */; }; B6B5726227AC14B60069DBA7 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5726127AC14B60069DBA7 /* TextView.swift */; }; B6B5726427AC14D10069DBA7 /* RegexTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5726327AC14D10069DBA7 /* RegexTextView.swift */; }; B6B5728027ACC3AF0069DBA7 /* ChecksumGeneratorView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5727F27ACC3AE0069DBA7 /* ChecksumGeneratorView+.swift */; }; B6B5728227ACCF2F0069DBA7 /* XMLFormatterView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5728127ACCF2F0069DBA7 /* XMLFormatterView+.swift */; }; B6B5728827ACD2F20069DBA7 /* Kanna in Frameworks */ = {isa = PBXBuildFile; productRef = B6B5728727ACD2F20069DBA7 /* Kanna */; }; B6B5728A27ACDA420069DBA7 /* ImageConverterView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5728927ACDA420069DBA7 /* ImageConverterView+.swift */; }; B6B5728C27ACE5600069DBA7 /* ImageConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5728B27ACE5600069DBA7 /* ImageConverter.swift */; }; B6B5728F27ACFA6E0069DBA7 /* ImageDropper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5728E27ACFA6D0069DBA7 /* ImageDropper.swift */; }; B6BBA90727C392E3000FE7D3 /* AudioConverterView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BBA90627C392E3000FE7D3 /* AudioConverterView+.swift */; }; B6BBA90927C39528000FE7D3 /* AudioConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BBA90827C39528000FE7D3 /* AudioConverter.swift */; }; B6BD80EB27B89EA600152AB9 /* ImageOptimaizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BD80EA27B89EA600152AB9 /* ImageOptimaizerView.swift */; }; B6BD80EF27B8A85400152AB9 /* ToolCategory+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BD80EE27B8A85400152AB9 /* ToolCategory+Default.swift */; }; B6BD80F127B8A86100152AB9 /* Tool+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BD80F027B8A86100152AB9 /* Tool+Default.swift */; }; B6BD80F427B8AEE700152AB9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6BD80F627B8AEE700152AB9 /* Localizable.strings */; }; B6C5292427C20D520019BCB0 /* GifConverterView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C5292327C20D520019BCB0 /* GifConverterView+.swift */; }; B6C5292727C20DC60019BCB0 /* FFMpegExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C5292627C20DC60019BCB0 /* FFMpegExecutor.swift */; }; B6CBFEF927CCA1B000902E56 /* squircle_bg_mask_256x256.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEE827CCA1B000902E56 /* squircle_bg_mask_256x256.png */; }; B6CBFEFA27CCA1B000902E56 /* squircle_bg_mask_1024x1024.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEE927CCA1B000902E56 /* squircle_bg_mask_1024x1024.png */; }; B6CBFEFB27CCA1B000902E56 /* squircle_back_128x128.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEEA27CCA1B000902E56 /* squircle_back_128x128.png */; }; B6CBFEFC27CCA1B000902E56 /* squircle_back_1024x1024.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEEB27CCA1B000902E56 /* squircle_back_1024x1024.png */; }; B6CBFEFD27CCA1B000902E56 /* squircle_back_16x16.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEEC27CCA1B000902E56 /* squircle_back_16x16.png */; }; B6CBFEFE27CCA1B000902E56 /* squircle_mask_256x256.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEED27CCA1B000902E56 /* squircle_mask_256x256.png */; }; B6CBFEFF27CCA1B000902E56 /* squircle_mask_16x16.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEEE27CCA1B000902E56 /* squircle_mask_16x16.png */; }; B6CBFF0027CCA1B000902E56 /* squircle_back_512x512.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEEF27CCA1B000902E56 /* squircle_back_512x512.png */; }; B6CBFF0127CCA1B000902E56 /* squircle_mask_64x64.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF027CCA1B000902E56 /* squircle_mask_64x64.png */; }; B6CBFF0227CCA1B000902E56 /* squircle_mask_128x128.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF127CCA1B000902E56 /* squircle_mask_128x128.png */; }; B6CBFF0327CCA1B000902E56 /* squircle_mask_32x32.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF227CCA1B000902E56 /* squircle_mask_32x32.png */; }; B6CBFF0427CCA1B000902E56 /* squircle_bg_mask_512x512.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF327CCA1B000902E56 /* squircle_bg_mask_512x512.png */; }; B6CBFF0527CCA1B000902E56 /* squircle_mask_1024x1024.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF427CCA1B000902E56 /* squircle_mask_1024x1024.png */; }; B6CBFF0627CCA1B000902E56 /* squircle_back_32x32.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF527CCA1B000902E56 /* squircle_back_32x32.png */; }; B6CBFF0727CCA1B000902E56 /* squircle_back_256x256.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF627CCA1B000902E56 /* squircle_back_256x256.png */; }; B6CBFF0827CCA1B000902E56 /* squircle_back_64x64.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF727CCA1B000902E56 /* squircle_back_64x64.png */; }; B6CBFF0927CCA1B000902E56 /* squircle_mask_512x512.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF827CCA1B000902E56 /* squircle_mask_512x512.png */; }; B6CBFF1727CCA88F00902E56 /* external_256x256.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFF0D27CCA88E00902E56 /* external_256x256.png */; }; B6CBFF1827CCA88F00902E56 /* external_128x128.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFF0E27CCA88E00902E56 /* external_128x128.png */; }; B6CBFF1927CCA88F00902E56 /* external_64x64.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFF0F27CCA88E00902E56 /* external_64x64.png */; }; B6CBFF1A27CCA88F00902E56 /* external_16x16.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFF1027CCA88E00902E56 /* external_16x16.png */; }; B6CBFF1B27CCA88F00902E56 /* external_32x32.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFF1127CCA88E00902E56 /* external_32x32.png */; }; B6CBFF1D27CCA88F00902E56 /* external_512x512.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFF1327CCA88E00902E56 /* external_512x512.png */; }; B6CBFF1F27CCA88F00902E56 /* external_1024x1024.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFF1527CCA88F00902E56 /* external_1024x1024.png */; }; B6D1AEDF27A534960022FED2 /* BodyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D1AEDE27A534960022FED2 /* BodyViewController.swift */; }; B6D1AEE427A53A560022FED2 /* HomeView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D1AEE327A53A560022FED2 /* HomeView+.swift */; }; B6D1AEE927A545230022FED2 /* Area.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D1AEE827A545230022FED2 /* Area.swift */; }; B6D1AEEC27A546810022FED2 /* ControlBackgroundLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D1AEEB27A546810022FED2 /* ControlBackgroundLayer.swift */; }; B6D1AEF027A547B90022FED2 /* JSONFormatterView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D1AEEF27A547B90022FED2 /* JSONFormatterView+.swift */; }; B6D1AEF427A54C440022FED2 /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D1AEF327A54C440022FED2 /* Section.swift */; }; B6D1AEF727A54F750022FED2 /* SectionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D1AEF627A54F750022FED2 /* SectionButton.swift */; }; B6D1AEFA27A557280022FED2 /* PopupButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D1AEF927A557280022FED2 /* PopupButton.swift */; }; B6D1AEFD27A55ED50022FED2 /* CodeTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D1AEFC27A55ED50022FED2 /* CodeTextView.swift */; }; B6D1AF0627A5603B0022FED2 /* Highlightr in Frameworks */ = {isa = PBXBuildFile; productRef = B6D1AF0527A5603B0022FED2 /* Highlightr */; }; B6D88A8B27D07204002E71EA /* CameraViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D88A8A27D07204002E71EA /* CameraViewController.swift */; }; B6E15A9427B6745100DC4D6B /* libimageoptimjpeg.dylib in Resources */ = {isa = PBXBuildFile; fileRef = B6E15A9227B6742300DC4D6B /* libimageoptimjpeg.dylib */; }; B6E294B327C3351F00314132 /* FFTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E294B227C3351F00314132 /* FFTask.swift */; }; B6E294E527C337F300314132 /* FFThumnailGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E294E427C337F300314132 /* FFThumnailGenerator.swift */; }; B6ED863627BFE7DC00A17474 /* DefaultImageExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ED863527BFE7DC00A17474 /* DefaultImageExporter.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ B64B1F5D27A4FC1900AC2601 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B64B1F5927A4FC1800AC2601 /* CoreUtil.xcodeproj */; proxyType = 2; remoteGlobalIDString = B64B1E8F27A4F67000AC2601; remoteInfo = CoreUtil; }; B64B1F5F27A4FC1F00AC2601 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B64B1F5927A4FC1800AC2601 /* CoreUtil.xcodeproj */; proxyType = 1; remoteGlobalIDString = B64B1E8E27A4F67000AC2601; remoteInfo = CoreUtil; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ B608541727A66CC9003BF243 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( B608541627A66CC9003BF243 /* CoreUtil.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 6B42318727AD1BC0002D135A /* HyphenationRemoverView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HyphenationRemoverView+.swift"; sourceTree = ""; }; B608540A27A66694003BF243 /* SectionButton+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SectionButton+.swift"; sourceTree = ""; }; B608541127A66A90003BF243 /* JSONYamlConverterView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONYamlConverterView+.swift"; sourceTree = ""; }; B608541D27A67B48003BF243 /* NumberBaseConverterView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NumberBaseConverterView+.swift"; sourceTree = ""; }; B608541F27A67E4D003BF243 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; B6113ECC27C4B96C00ACC6E8 /* EmptyImageTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyImageTableView.swift; sourceTree = ""; }; B6113ECE27C4BF2300ACC6E8 /* FileConflictAvoider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileConflictAvoider.swift; sourceTree = ""; }; B6113F0027C4BF7700ACC6E8 /* Identifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Identifier.swift; sourceTree = ""; }; B61157BF27C8C3F9004D77A5 /* JSONSearchView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONSearchView+.swift"; sourceTree = ""; }; B61157C227C8C78A004D77A5 /* JSONNormalSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONNormalSearchView.swift; sourceTree = ""; }; B61157C427C8D77A004D77A5 /* DragImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DragImageView.swift; sourceTree = ""; }; B61796C427BCB3090054660E /* ToolCategoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolCategoryCell.swift; sourceTree = ""; }; B61796C627BCB6F00054660E /* SettingView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingView+.swift"; sourceTree = ""; }; B61796C827BCB84A0054660E /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; B62013B327A9070D00AF5386 /* UUIDGeneratorView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UUIDGeneratorView+.swift"; sourceTree = ""; }; B62013B527A90B8900AF5386 /* NumberField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberField.swift; sourceTree = ""; }; B620141B27A917E000AF5386 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B620141D27A91F6700AF5386 /* LoremIpsumGeneratorView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoremIpsumGeneratorView+.swift"; sourceTree = ""; }; B620142027A9271500AF5386 /* TextInspectorView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextInspectorView+.swift"; sourceTree = ""; }; B620142227A929B400AF5386 /* TagCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagCloudView.swift; sourceTree = ""; }; B627294E27BFB7680034D70C /* WebpImageExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebpImageExporter.swift; sourceTree = ""; }; B627295127BFB8CB0034D70C /* cwebp */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = cwebp; sourceTree = ""; }; B627295627BFCCC40034D70C /* HeicImageExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeicImageExporter.swift; sourceTree = ""; }; B63A034A27C2130A009FD2AD /* GifConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifConverter.swift; sourceTree = ""; }; B63A034C27C21A99009FD2AD /* ffmpeg */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = ffmpeg; sourceTree = ""; }; B63A034F27C24BA0009FD2AD /* FFProgressReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FFProgressReport.swift; sourceTree = ""; }; B63A035127C25B29009FD2AD /* FFTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FFTime.swift; sourceTree = ""; }; B64844FE27B767EC004FE02B /* ToolManager+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ToolManager+.swift"; sourceTree = ""; }; B64AA44F27AD0D3900EC436D /* DateConverterView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateConverterView+.swift"; sourceTree = ""; }; B64B1E7027A4F5E200AC2601 /* DevToys.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DevToys.app; sourceTree = BUILT_PRODUCTS_DIR; }; B64B1E7327A4F5E200AC2601 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; B64B1E7527A4F5E200AC2601 /* AppViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewController.swift; sourceTree = ""; }; B64B1E7727A4F5E300AC2601 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B64B1E7A27A4F5E300AC2601 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; B64B1E7C27A4F5E300AC2601 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B64B1E7D27A4F5E300AC2601 /* DevToys.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DevToys.entitlements; sourceTree = ""; }; B64B1F5927A4FC1800AC2601 /* CoreUtil.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CoreUtil.xcodeproj; path = ../CoreUtil/CoreUtil.xcodeproj; sourceTree = ""; }; B64B1F7127A4FDC800AC2601 /* AppModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppModel.swift; sourceTree = ""; }; B64B1F7627A4FF8B00AC2601 /* SidebarView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SidebarView+.swift"; sourceTree = ""; }; B64B201527A5100800AC2601 /* Separator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Separator.swift; sourceTree = ""; }; B64B201C27A5219400AC2601 /* SearchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchCell.swift; sourceTree = ""; }; B64B201F27A523A200AC2601 /* ToolMenuCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolMenuCell.swift; sourceTree = ""; }; B64B202227A52A2D00AC2601 /* R.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = R.swift; sourceTree = ""; }; B64B209727A532DF00AC2601 /* AppWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppWindowController.swift; sourceTree = ""; }; B65DB77F27AC0EB400146A3C /* RegexTesterView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RegexTesterView+.swift"; sourceTree = ""; }; B66536CD27C9BA4000CA1CEC /* Slugify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Slugify.swift; sourceTree = ""; }; B66536CF27C9C56600CA1CEC /* SQLFormatterView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SQLFormatterView+.swift"; sourceTree = ""; }; B66850ED27A64D3200A3FE01 /* Page.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Page.swift; sourceTree = ""; }; B66BE8BB27C49AD4003B5ED0 /* AudioFileScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioFileScanner.swift; sourceTree = ""; }; B672CF9F27AAB8DD00391A5D /* FileDrop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDrop.swift; sourceTree = ""; }; B673A90127AD0E78005512AB /* DatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePicker.swift; sourceTree = ""; }; B6789EB927CA43E100D8B58C /* IconGeneratorView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IconGeneratorView+.swift"; sourceTree = ""; }; B6789EBB27CA48A300D8B58C /* IosIconGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosIconGenerator.swift; sourceTree = ""; }; B6789EDE27CA5F5D00D8B58C /* IconsetGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconsetGenerator.swift; sourceTree = ""; }; B6789EE027CA5F6900D8B58C /* IcnsGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IcnsGenerator.swift; sourceTree = ""; }; B6789EE227CA5F7A00D8B58C /* IconFolderGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconFolderGenerator.swift; sourceTree = ""; }; B6789EE427CA5F9500D8B58C /* IconGenerator+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IconGenerator+Model.swift"; sourceTree = ""; }; B6789EE627CB334D00D8B58C /* AndroidIconGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AndroidIconGenerator.swift; sourceTree = ""; }; B67A4C0227C0B9FF009277FB /* ColorPickerHandleLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPickerHandleLayer.swift; sourceTree = ""; }; B67A4C0327C0B9FF009277FB /* OpacityBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpacityBarView.swift; sourceTree = ""; }; B67A4C0427C0B9FF009277FB /* HueBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HueBarView.swift; sourceTree = ""; }; B67A4C0527C0B9FF009277FB /* ColorPickerView+.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ColorPickerView+.swift"; sourceTree = ""; }; B67A4C0627C0B9FF009277FB /* ColorBoxView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorBoxView.swift; sourceTree = ""; }; B67A4C0727C0B9FF009277FB /* Color.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; B67A4C3F27C0EA84009277FB /* ACPixelPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ACPixelPicker.swift; sourceTree = ""; }; B67A4C4027C0EA84009277FB /* ACOverlayPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ACOverlayPreview.swift; sourceTree = ""; }; B67A4C4227C0EA84009277FB /* Ex+NSBezierPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+NSBezierPath.swift"; sourceTree = ""; }; B67A4C4327C0EA84009277FB /* ShowAndHideCursor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShowAndHideCursor.h; sourceTree = ""; }; B67A4C4427C0EA84009277FB /* Util+PixelPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Util+PixelPicker.swift"; sourceTree = ""; }; B67A4C4527C0EA84009277FB /* ShowAndHideCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShowAndHideCursor.swift; sourceTree = ""; }; B67A4C4627C0EA84009277FB /* ShowAndHideCursor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShowAndHideCursor.m; sourceTree = ""; }; B67A4C4727C0EA84009277FB /* Ex+NSColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+NSColor.swift"; sourceTree = ""; }; B67A4C4827C0EA84009277FB /* Ex+CGImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Ex+CGImage.swift"; sourceTree = ""; }; B67A4C4927C0EA84009277FB /* ACOverlayPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ACOverlayPanel.swift; sourceTree = ""; }; B67A4C4A27C0EA84009277FB /* ACOverlayController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ACOverlayController.swift; sourceTree = ""; }; B67A4C4B27C0EA84009277FB /* ACOverlayController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ACOverlayController.xib; sourceTree = ""; }; B67A4C4C27C0EA84009277FB /* ACOverlayWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ACOverlayWrapper.swift; sourceTree = ""; }; B67A4C5C27C0EB51009277FB /* Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = ""; }; B67A4CBD27C0EEEF009277FB /* ColorSampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorSampleView.swift; sourceTree = ""; }; B67A4CBF27C0F4BA009277FB /* BoxHSBColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxHSBColorPicker.swift; sourceTree = ""; }; B67A4CC227C0F4D4009277FB /* CircleHueBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleHueBarView.swift; sourceTree = ""; }; B67A4CC427C0F50D009277FB /* R+ColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "R+ColorPicker.swift"; sourceTree = ""; }; B67A4CC627C0F546009277FB /* CircleBoxHSBColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleBoxHSBColorPicker.swift; sourceTree = ""; }; B67A4CC827C10493009277FB /* SaturationBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaturationBarView.swift; sourceTree = ""; }; B67A4CCA27C10548009277FB /* BrightnessBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrightnessBarView.swift; sourceTree = ""; }; B67A4CCC27C12C09009277FB /* CircleBarsHSBColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleBarsHSBColorPicker.swift; sourceTree = ""; }; B67A4CCE27C1E010009277FB /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Main.strings"; sourceTree = ""; }; B67A4CCF27C1E01C009277FB /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; B67A4CD027C1E036009277FB /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Main.strings"; sourceTree = ""; }; B67A4CD127C1E03C009277FB /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; B67A4CD427C1E0DC009277FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Main.strings; sourceTree = ""; }; B67A4CD527C1E0DF009277FB /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Main.strings; sourceTree = ""; }; B67A4CD727C1E0F1009277FB /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Main.strings; sourceTree = ""; }; B67A4CD827C1E0F8009277FB /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; B67A4CD927C1E469009277FB /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Main.strings; sourceTree = ""; }; B67A4CDA27C1E46A009277FB /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; B680A79427A68B35007CB707 /* HTMLDecoderView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HTMLDecoderView+.swift"; sourceTree = ""; }; B680A79927A69324007CB707 /* URLDecoderView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLDecoderView+.swift"; sourceTree = ""; }; B680A79B27A6947D007CB707 /* Base64DecoderView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Base64DecoderView+.swift"; sourceTree = ""; }; B680A83227A8BF7D007CB707 /* TextViewSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewSection.swift; sourceTree = ""; }; B680A86727A8D4D1007CB707 /* TextFieldSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldSection.swift; sourceTree = ""; }; B680A89927A8D9DD007CB707 /* Toast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = ""; }; B680A8A627A8DD5D007CB707 /* JWTDecoderView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JWTDecoderView+.swift"; sourceTree = ""; }; B680A8A927A8E106007CB707 /* HashGeneratorView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashGeneratorView+.swift"; sourceTree = ""; }; B684EA2727C5EFE90014802F /* TextDiffView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextDiffView+.swift"; sourceTree = ""; }; B684EA2927C609620014802F /* QRCodeGeneratorView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QRCodeGeneratorView+.swift"; sourceTree = ""; }; B69980D327CCCF0A0063F63D /* android_mask.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = android_mask.png; sourceTree = ""; }; B69980D627CCE09A0063F63D /* IcoGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IcoGenerator.swift; sourceTree = ""; }; B69980D827CCEB320063F63D /* PngIconGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PngIconGenerator.swift; sourceTree = ""; }; B69F0E7C27CBC20F0032F96A /* folder_back_64_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_back_64_bs.png; sourceTree = ""; }; B69F0E7D27CBC20F0032F96A /* folder_back_128_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_back_128_bs.png; sourceTree = ""; }; B69F0E7E27CBC20F0032F96A /* folder_back_256_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_back_256_bs.png; sourceTree = ""; }; B69F0E7F27CBC20F0032F96A /* folder_back_1024_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_back_1024_bs.png; sourceTree = ""; }; B69F0E8027CBC20F0032F96A /* folder_back_32_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_back_32_bs.png; sourceTree = ""; }; B69F0E8127CBC20F0032F96A /* folder_back_16_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_back_16_bs.png; sourceTree = ""; }; B69F0E8227CBC20F0032F96A /* folder_back_512_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_back_512_bs.png; sourceTree = ""; }; B69F0E8B27CBC2340032F96A /* folder_mask2_1024_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_mask2_1024_bs.png; sourceTree = ""; }; B69F0E8C27CBC2340032F96A /* folder_mask2_512_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_mask2_512_bs.png; sourceTree = ""; }; B69F0E8D27CBC2340032F96A /* folder_mask2_16_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_mask2_16_bs.png; sourceTree = ""; }; B69F0E8E27CBC2340032F96A /* folder_mask2_32_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_mask2_32_bs.png; sourceTree = ""; }; B69F0E8F27CBC2340032F96A /* folder_mask2_64_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_mask2_64_bs.png; sourceTree = ""; }; B69F0E9027CBC2340032F96A /* folder_mask2_128_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_mask2_128_bs.png; sourceTree = ""; }; B69F0E9727CBC2800032F96A /* folder_mask2_256_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_mask2_256_bs.png; sourceTree = ""; }; B69F0E9A27CBC7550032F96A /* folder_top_1024.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_top_1024.png; sourceTree = ""; }; B69F0E9B27CBC7550032F96A /* folder_top_512.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_top_512.png; sourceTree = ""; }; B69F0E9E27CC76A50032F96A /* IconImageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconImageManager.swift; sourceTree = ""; }; B69F0EA427CC7CF40032F96A /* watermark_mask_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = watermark_mask_bs.png; sourceTree = ""; }; B69F0EA527CC7CF40032F96A /* watermark_mask_dark_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = watermark_mask_dark_bs.png; sourceTree = ""; }; B6A4F2A127CD05F000BBDE7E /* QRCodeReaderView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QRCodeReaderView+.swift"; sourceTree = ""; }; B6A4F2A327CD078100BBDE7E /* ImageDropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDropView.swift; sourceTree = ""; }; B6A93D2F27CBBC97003A6D7F /* IconTemplete+.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IconTemplete+.swift"; sourceTree = ""; }; B6A93D3027CBBC97003A6D7F /* IconSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IconSet.swift; sourceTree = ""; }; B6A93D3127CBBC97003A6D7F /* IconTemplete.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IconTemplete.swift; sourceTree = ""; }; B6AC272F27AA1373000FD713 /* optipng */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = optipng; sourceTree = ""; }; B6AC273127AA2BF6000FD713 /* ImageOptimizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageOptimizer.swift; sourceTree = ""; }; B6AC276427AA3D61000FD713 /* jpegoptim */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = jpegoptim; sourceTree = ""; }; B6AC276927AA535A000FD713 /* PDFGeneratorView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PDFGeneratorView+.swift"; sourceTree = ""; }; B6B5726127AC14B60069DBA7 /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = ""; }; B6B5726327AC14D10069DBA7 /* RegexTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegexTextView.swift; sourceTree = ""; }; B6B5727F27ACC3AE0069DBA7 /* ChecksumGeneratorView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChecksumGeneratorView+.swift"; sourceTree = ""; }; B6B5728127ACCF2F0069DBA7 /* XMLFormatterView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XMLFormatterView+.swift"; sourceTree = ""; }; B6B5728927ACDA420069DBA7 /* ImageConverterView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImageConverterView+.swift"; sourceTree = ""; }; B6B5728B27ACE5600069DBA7 /* ImageConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageConverter.swift; sourceTree = ""; }; B6B5728E27ACFA6D0069DBA7 /* ImageDropper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDropper.swift; sourceTree = ""; }; B6BBA90627C392E3000FE7D3 /* AudioConverterView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AudioConverterView+.swift"; sourceTree = ""; }; B6BBA90827C39528000FE7D3 /* AudioConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioConverter.swift; sourceTree = ""; }; B6BD80EA27B89EA600152AB9 /* ImageOptimaizerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageOptimaizerView.swift; sourceTree = ""; }; B6BD80EE27B8A85400152AB9 /* ToolCategory+Default.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ToolCategory+Default.swift"; sourceTree = ""; }; B6BD80F027B8A86100152AB9 /* Tool+Default.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tool+Default.swift"; sourceTree = ""; }; B6BD80F527B8AEE700152AB9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; B6BD80F827B8AF1800152AB9 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; B6C5292327C20D520019BCB0 /* GifConverterView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GifConverterView+.swift"; sourceTree = ""; }; B6C5292627C20DC60019BCB0 /* FFMpegExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FFMpegExecutor.swift; sourceTree = ""; }; B6CBFEE827CCA1B000902E56 /* squircle_bg_mask_256x256.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_bg_mask_256x256.png; sourceTree = ""; }; B6CBFEE927CCA1B000902E56 /* squircle_bg_mask_1024x1024.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_bg_mask_1024x1024.png; sourceTree = ""; }; B6CBFEEA27CCA1B000902E56 /* squircle_back_128x128.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_back_128x128.png; sourceTree = ""; }; B6CBFEEB27CCA1B000902E56 /* squircle_back_1024x1024.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_back_1024x1024.png; sourceTree = ""; }; B6CBFEEC27CCA1B000902E56 /* squircle_back_16x16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_back_16x16.png; sourceTree = ""; }; B6CBFEED27CCA1B000902E56 /* squircle_mask_256x256.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_mask_256x256.png; sourceTree = ""; }; B6CBFEEE27CCA1B000902E56 /* squircle_mask_16x16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_mask_16x16.png; sourceTree = ""; }; B6CBFEEF27CCA1B000902E56 /* squircle_back_512x512.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_back_512x512.png; sourceTree = ""; }; B6CBFEF027CCA1B000902E56 /* squircle_mask_64x64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_mask_64x64.png; sourceTree = ""; }; B6CBFEF127CCA1B000902E56 /* squircle_mask_128x128.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_mask_128x128.png; sourceTree = ""; }; B6CBFEF227CCA1B000902E56 /* squircle_mask_32x32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_mask_32x32.png; sourceTree = ""; }; B6CBFEF327CCA1B000902E56 /* squircle_bg_mask_512x512.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_bg_mask_512x512.png; sourceTree = ""; }; B6CBFEF427CCA1B000902E56 /* squircle_mask_1024x1024.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_mask_1024x1024.png; sourceTree = ""; }; B6CBFEF527CCA1B000902E56 /* squircle_back_32x32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_back_32x32.png; sourceTree = ""; }; B6CBFEF627CCA1B000902E56 /* squircle_back_256x256.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_back_256x256.png; sourceTree = ""; }; B6CBFEF727CCA1B000902E56 /* squircle_back_64x64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_back_64x64.png; sourceTree = ""; }; B6CBFEF827CCA1B000902E56 /* squircle_mask_512x512.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_mask_512x512.png; sourceTree = ""; }; B6CBFF0D27CCA88E00902E56 /* external_256x256.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = external_256x256.png; sourceTree = ""; }; B6CBFF0E27CCA88E00902E56 /* external_128x128.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = external_128x128.png; sourceTree = ""; }; B6CBFF0F27CCA88E00902E56 /* external_64x64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = external_64x64.png; sourceTree = ""; }; B6CBFF1027CCA88E00902E56 /* external_16x16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = external_16x16.png; sourceTree = ""; }; B6CBFF1127CCA88E00902E56 /* external_32x32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = external_32x32.png; sourceTree = ""; }; B6CBFF1327CCA88E00902E56 /* external_512x512.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = external_512x512.png; sourceTree = ""; }; B6CBFF1527CCA88F00902E56 /* external_1024x1024.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = external_1024x1024.png; sourceTree = ""; }; B6D1AEDE27A534960022FED2 /* BodyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyViewController.swift; sourceTree = ""; }; B6D1AEE327A53A560022FED2 /* HomeView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeView+.swift"; sourceTree = ""; }; B6D1AEE827A545230022FED2 /* Area.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Area.swift; sourceTree = ""; }; B6D1AEEB27A546810022FED2 /* ControlBackgroundLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlBackgroundLayer.swift; sourceTree = ""; }; B6D1AEEF27A547B90022FED2 /* JSONFormatterView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONFormatterView+.swift"; sourceTree = ""; }; B6D1AEF327A54C440022FED2 /* Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; B6D1AEF627A54F750022FED2 /* SectionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionButton.swift; sourceTree = ""; }; B6D1AEF927A557280022FED2 /* PopupButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupButton.swift; sourceTree = ""; }; B6D1AEFC27A55ED50022FED2 /* CodeTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeTextView.swift; sourceTree = ""; }; B6D88A8A27D07204002E71EA /* CameraViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraViewController.swift; sourceTree = ""; }; B6E15A9227B6742300DC4D6B /* libimageoptimjpeg.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libimageoptimjpeg.dylib; sourceTree = ""; }; B6E294B227C3351F00314132 /* FFTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FFTask.swift; sourceTree = ""; }; B6E294E427C337F300314132 /* FFThumnailGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FFThumnailGenerator.swift; sourceTree = ""; }; B6ED863527BFE7DC00A17474 /* DefaultImageExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultImageExporter.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ B64B1E6D27A4F5E200AC2601 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( B684EA2627C5EFDB0014802F /* DiffMatchPatch in Frameworks */, B6D1AF0627A5603B0022FED2 /* Highlightr in Frameworks */, B6113F0927C4C6C900ACC6E8 /* Sparkle in Frameworks */, B608541527A66CC9003BF243 /* CoreUtil.framework in Frameworks */, B680A79827A69268007CB707 /* HTMLEntities in Frameworks */, B608541B27A672E3003BF243 /* Yams in Frameworks */, B6B5728827ACD2F20069DBA7 /* Kanna in Frameworks */, B66850F227A65FC200A3FE01 /* SwiftJSONFormatter in Frameworks */, B680A8AD27A8E31C007CB707 /* CryptoSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ B608541427A66CC9003BF243 /* Frameworks */ = { isa = PBXGroup; children = ( ); name = Frameworks; sourceTree = ""; }; B608542127A67FF7003BF243 /* Convert */ = { isa = PBXGroup; children = ( B608541127A66A90003BF243 /* JSONYamlConverterView+.swift */, B608541D27A67B48003BF243 /* NumberBaseConverterView+.swift */, B64AA44F27AD0D3900EC436D /* DateConverterView+.swift */, ); path = Convert; sourceTree = ""; }; B608542227A68008003BF243 /* Format */ = { isa = PBXGroup; children = ( B6D1AEEF27A547B90022FED2 /* JSONFormatterView+.swift */, B6B5728127ACCF2F0069DBA7 /* XMLFormatterView+.swift */, B66536CF27C9C56600CA1CEC /* SQLFormatterView+.swift */, ); path = Format; sourceTree = ""; }; B61157C127C8C76A004D77A5 /* JSON Search */ = { isa = PBXGroup; children = ( B61157BF27C8C3F9004D77A5 /* JSONSearchView+.swift */, B61157C227C8C78A004D77A5 /* JSONNormalSearchView.swift */, ); path = "JSON Search"; sourceTree = ""; }; B620141F27A926B400AF5386 /* Text */ = { isa = PBXGroup; children = ( B61157C127C8C76A004D77A5 /* JSON Search */, B620142027A9271500AF5386 /* TextInspectorView+.swift */, B65DB77F27AC0EB400146A3C /* RegexTesterView+.swift */, 6B42318727AD1BC0002D135A /* HyphenationRemoverView+.swift */, B684EA2727C5EFE90014802F /* TextDiffView+.swift */, ); path = Text; sourceTree = ""; }; B620145427A9440E00AF5386 /* Graphic */ = { isa = PBXGroup; children = ( B6AC276927AA535A000FD713 /* PDFGeneratorView+.swift */, B6A4F2A127CD05F000BBDE7E /* QRCodeReaderView+.swift */, B6789EB827CA438800D8B58C /* Icon Generator */, B6AC276827AA532B000FD713 /* Image Optimizer */, B6B5729027ACFE020069DBA7 /* Image Converter */, ); path = Graphic; sourceTree = ""; }; B63A034927C212FE009FD2AD /* Movie to Gif */ = { isa = PBXGroup; children = ( B6C5292327C20D520019BCB0 /* GifConverterView+.swift */, B63A034A27C2130A009FD2AD /* GifConverter.swift */, ); path = "Movie to Gif"; sourceTree = ""; }; B64B1E6727A4F5E200AC2601 = { isa = PBXGroup; children = ( B64B1F5927A4FC1800AC2601 /* CoreUtil.xcodeproj */, B64B1E7227A4F5E200AC2601 /* DevToys */, B64B1E7127A4F5E200AC2601 /* Products */, B608541427A66CC9003BF243 /* Frameworks */, ); sourceTree = ""; }; B64B1E7127A4F5E200AC2601 /* Products */ = { isa = PBXGroup; children = ( B64B1E7027A4F5E200AC2601 /* DevToys.app */, ); name = Products; sourceTree = ""; }; B64B1E7227A4F5E200AC2601 /* DevToys */ = { isa = PBXGroup; children = ( B64B1E7D27A4F5E300AC2601 /* DevToys.entitlements */, B64B1E7C27A4F5E300AC2601 /* Info.plist */, B64B1E7327A4F5E200AC2601 /* AppDelegate.swift */, B67A4C5C27C0EB51009277FB /* Bridging-Header.h */, B64B1E9D27A4F6A700AC2601 /* Resource */, B64B1F7027A4FDBC00AC2601 /* Model */, B64B209A27A532E300AC2601 /* App */, B64B1F7527A4FF8700AC2601 /* Sidebar */, B6D1AEE727A544F20022FED2 /* Component */, B6D1AEDD27A534850022FED2 /* Body */, ); path = DevToys; sourceTree = ""; }; B64B1E9D27A4F6A700AC2601 /* Resource */ = { isa = PBXGroup; children = ( B64B1E7927A4F5E300AC2601 /* Main.storyboard */, B64B1E7727A4F5E300AC2601 /* Assets.xcassets */, B64B202227A52A2D00AC2601 /* R.swift */, B6BD80F627B8AEE700152AB9 /* Localizable.strings */, ); path = Resource; sourceTree = ""; }; B64B1F5A27A4FC1800AC2601 /* Products */ = { isa = PBXGroup; children = ( B64B1F5E27A4FC1900AC2601 /* CoreUtil.framework */, ); name = Products; sourceTree = ""; }; B64B1F7027A4FDBC00AC2601 /* Model */ = { isa = PBXGroup; children = ( B64B1F7127A4FDC800AC2601 /* AppModel.swift */, B61796C827BCB84A0054660E /* Settings.swift */, B64844FE27B767EC004FE02B /* ToolManager+.swift */, B6BD80EE27B8A85400152AB9 /* ToolCategory+Default.swift */, B6BD80F027B8A86100152AB9 /* Tool+Default.swift */, ); path = Model; sourceTree = ""; }; B64B1F7527A4FF8700AC2601 /* Sidebar */ = { isa = PBXGroup; children = ( B64B201427A50FFF00AC2601 /* View */, B64B1F7627A4FF8B00AC2601 /* SidebarView+.swift */, B64B201F27A523A200AC2601 /* ToolMenuCell.swift */, B61796C427BCB3090054660E /* ToolCategoryCell.swift */, B64B201C27A5219400AC2601 /* SearchCell.swift */, ); path = Sidebar; sourceTree = ""; }; B64B201427A50FFF00AC2601 /* View */ = { isa = PBXGroup; children = ( B64B201527A5100800AC2601 /* Separator.swift */, ); path = View; sourceTree = ""; }; B64B209A27A532E300AC2601 /* App */ = { isa = PBXGroup; children = ( B64B209727A532DF00AC2601 /* AppWindowController.swift */, B64B1E7527A4F5E200AC2601 /* AppViewController.swift */, ); path = App; sourceTree = ""; }; B66BE8BD27C49B28003B5ED0 /* Utils */ = { isa = PBXGroup; children = ( B6D88A8A27D07204002E71EA /* CameraViewController.swift */, B6113F0027C4BF7700ACC6E8 /* Identifier.swift */, B6113ECE27C4BF2300ACC6E8 /* FileConflictAvoider.swift */, B6B5728E27ACFA6D0069DBA7 /* ImageDropper.swift */, B6C5292527C20DAF0019BCB0 /* ffmpeg */, B66536CD27C9BA4000CA1CEC /* Slugify.swift */, ); path = Utils; sourceTree = ""; }; B6789EB827CA438800D8B58C /* Icon Generator */ = { isa = PBXGroup; children = ( B6789EB927CA43E100D8B58C /* IconGeneratorView+.swift */, B6789EE827CB3D6300D8B58C /* Resource */, B6A93D2E27CBBC97003A6D7F /* Icon Templete */, B6789EDD27CA5F5400D8B58C /* Generators */, ); path = "Icon Generator"; sourceTree = ""; }; B6789EDD27CA5F5400D8B58C /* Generators */ = { isa = PBXGroup; children = ( B6789EBB27CA48A300D8B58C /* IosIconGenerator.swift */, B6789EDE27CA5F5D00D8B58C /* IconsetGenerator.swift */, B6789EE027CA5F6900D8B58C /* IcnsGenerator.swift */, B6789EE227CA5F7A00D8B58C /* IconFolderGenerator.swift */, B6789EE427CA5F9500D8B58C /* IconGenerator+Model.swift */, B6789EE627CB334D00D8B58C /* AndroidIconGenerator.swift */, B69980D627CCE09A0063F63D /* IcoGenerator.swift */, B69980D827CCEB320063F63D /* PngIconGenerator.swift */, ); path = Generators; sourceTree = ""; }; B6789EE827CB3D6300D8B58C /* Resource */ = { isa = PBXGroup; children = ( B6A93CC527CB9C0D003A6D7F /* Folder */, ); path = Resource; sourceTree = ""; }; B67A4C0127C0B9FF009277FB /* Color Picker */ = { isa = PBXGroup; children = ( B67A4C0727C0B9FF009277FB /* Color.swift */, B67A4CC427C0F50D009277FB /* R+ColorPicker.swift */, B67A4C0527C0B9FF009277FB /* ColorPickerView+.swift */, B67A4C3E27C0EA84009277FB /* Pixel Picker */, B67A4CC127C0F4BE009277FB /* Components */, ); path = "Color Picker"; sourceTree = ""; }; B67A4C3E27C0EA84009277FB /* Pixel Picker */ = { isa = PBXGroup; children = ( B67A4C3F27C0EA84009277FB /* ACPixelPicker.swift */, B67A4C4027C0EA84009277FB /* ACOverlayPreview.swift */, B67A4C4127C0EA84009277FB /* Utils */, B67A4C4927C0EA84009277FB /* ACOverlayPanel.swift */, B67A4C4A27C0EA84009277FB /* ACOverlayController.swift */, B67A4C4B27C0EA84009277FB /* ACOverlayController.xib */, B67A4C4C27C0EA84009277FB /* ACOverlayWrapper.swift */, ); path = "Pixel Picker"; sourceTree = ""; }; B67A4C4127C0EA84009277FB /* Utils */ = { isa = PBXGroup; children = ( B67A4C4227C0EA84009277FB /* Ex+NSBezierPath.swift */, B67A4C4427C0EA84009277FB /* Util+PixelPicker.swift */, B67A4C4527C0EA84009277FB /* ShowAndHideCursor.swift */, B67A4C4327C0EA84009277FB /* ShowAndHideCursor.h */, B67A4C4627C0EA84009277FB /* ShowAndHideCursor.m */, B67A4C4727C0EA84009277FB /* Ex+NSColor.swift */, B67A4C4827C0EA84009277FB /* Ex+CGImage.swift */, ); path = Utils; sourceTree = ""; }; B67A4CC127C0F4BE009277FB /* Components */ = { isa = PBXGroup; children = ( B67A4CBF27C0F4BA009277FB /* BoxHSBColorPicker.swift */, B67A4CC627C0F546009277FB /* CircleBoxHSBColorPicker.swift */, B67A4C0627C0B9FF009277FB /* ColorBoxView.swift */, B67A4C0227C0B9FF009277FB /* ColorPickerHandleLayer.swift */, B67A4CBD27C0EEEF009277FB /* ColorSampleView.swift */, B67A4C0327C0B9FF009277FB /* OpacityBarView.swift */, B67A4C0427C0B9FF009277FB /* HueBarView.swift */, B67A4CC227C0F4D4009277FB /* CircleHueBarView.swift */, B67A4CC827C10493009277FB /* SaturationBarView.swift */, B67A4CCA27C10548009277FB /* BrightnessBarView.swift */, B67A4CCC27C12C09009277FB /* CircleBarsHSBColorPicker.swift */, ); path = Components; sourceTree = ""; }; B680A79327A68B2D007CB707 /* Coder */ = { isa = PBXGroup; children = ( B680A79427A68B35007CB707 /* HTMLDecoderView+.swift */, B680A79927A69324007CB707 /* URLDecoderView+.swift */, B680A79B27A6947D007CB707 /* Base64DecoderView+.swift */, B680A8A627A8DD5D007CB707 /* JWTDecoderView+.swift */, ); path = Coder; sourceTree = ""; }; B680A8A827A8E0F8007CB707 /* Generator */ = { isa = PBXGroup; children = ( B680A8A927A8E106007CB707 /* HashGeneratorView+.swift */, B62013B327A9070D00AF5386 /* UUIDGeneratorView+.swift */, B620141D27A91F6700AF5386 /* LoremIpsumGeneratorView+.swift */, B6B5727F27ACC3AE0069DBA7 /* ChecksumGeneratorView+.swift */, B684EA2927C609620014802F /* QRCodeGeneratorView+.swift */, ); path = Generator; sourceTree = ""; }; B69F0E7B27CBC20F0032F96A /* folder_back */ = { isa = PBXGroup; children = ( B69F0E8127CBC20F0032F96A /* folder_back_16_bs.png */, B69F0E8027CBC20F0032F96A /* folder_back_32_bs.png */, B69F0E7C27CBC20F0032F96A /* folder_back_64_bs.png */, B69F0E7D27CBC20F0032F96A /* folder_back_128_bs.png */, B69F0E7E27CBC20F0032F96A /* folder_back_256_bs.png */, B69F0E8227CBC20F0032F96A /* folder_back_512_bs.png */, B69F0E7F27CBC20F0032F96A /* folder_back_1024_bs.png */, ); path = folder_back; sourceTree = ""; }; B69F0E8A27CBC2340032F96A /* folder_mask */ = { isa = PBXGroup; children = ( B69F0E8D27CBC2340032F96A /* folder_mask2_16_bs.png */, B69F0E8E27CBC2340032F96A /* folder_mask2_32_bs.png */, B69F0E8F27CBC2340032F96A /* folder_mask2_64_bs.png */, B69F0E9027CBC2340032F96A /* folder_mask2_128_bs.png */, B69F0E9727CBC2800032F96A /* folder_mask2_256_bs.png */, B69F0E8C27CBC2340032F96A /* folder_mask2_512_bs.png */, B69F0E8B27CBC2340032F96A /* folder_mask2_1024_bs.png */, ); path = folder_mask; sourceTree = ""; }; B69F0E9927CBC7550032F96A /* folder_top */ = { isa = PBXGroup; children = ( B69F0E9A27CBC7550032F96A /* folder_top_1024.png */, B69F0E9B27CBC7550032F96A /* folder_top_512.png */, ); path = folder_top; sourceTree = ""; }; B6A93CC527CB9C0D003A6D7F /* Folder */ = { isa = PBXGroup; children = ( B69980D327CCCF0A0063F63D /* android_mask.png */, B6CBFF0B27CCA88100902E56 /* external_drive */, B69F0EA427CC7CF40032F96A /* watermark_mask_bs.png */, B69F0EA527CC7CF40032F96A /* watermark_mask_dark_bs.png */, B6CBFEE727CCA1B000902E56 /* big_sur_icon */, B69F0E9927CBC7550032F96A /* folder_top */, B69F0E8A27CBC2340032F96A /* folder_mask */, B69F0E7B27CBC20F0032F96A /* folder_back */, ); path = Folder; sourceTree = ""; }; B6A93D2E27CBBC97003A6D7F /* Icon Templete */ = { isa = PBXGroup; children = ( B6A93D2F27CBBC97003A6D7F /* IconTemplete+.swift */, B6A93D3027CBBC97003A6D7F /* IconSet.swift */, B6A93D3127CBBC97003A6D7F /* IconTemplete.swift */, B69F0E9E27CC76A50032F96A /* IconImageManager.swift */, ); path = "Icon Templete"; sourceTree = ""; }; B6AC276827AA532B000FD713 /* Image Optimizer */ = { isa = PBXGroup; children = ( B6E15A9227B6742300DC4D6B /* libimageoptimjpeg.dylib */, B6AC276427AA3D61000FD713 /* jpegoptim */, B6AC272F27AA1373000FD713 /* optipng */, B6BD80EA27B89EA600152AB9 /* ImageOptimaizerView.swift */, B6AC273127AA2BF6000FD713 /* ImageOptimizer.swift */, ); path = "Image Optimizer"; sourceTree = ""; }; B6B5729027ACFE020069DBA7 /* Image Converter */ = { isa = PBXGroup; children = ( B6ED863427BFE12400A17474 /* Exporters */, B6B5728927ACDA420069DBA7 /* ImageConverterView+.swift */, B6B5728B27ACE5600069DBA7 /* ImageConverter.swift */, ); path = "Image Converter"; sourceTree = ""; }; B6BBA90427C391EE000FE7D3 /* Media */ = { isa = PBXGroup; children = ( B63A034927C212FE009FD2AD /* Movie to Gif */, B67A4C0127C0B9FF009277FB /* Color Picker */, B6BBA90527C392D5000FE7D3 /* Audio Converter */, ); path = Media; sourceTree = ""; }; B6BBA90527C392D5000FE7D3 /* Audio Converter */ = { isa = PBXGroup; children = ( B66BE8BB27C49AD4003B5ED0 /* AudioFileScanner.swift */, B6BBA90627C392E3000FE7D3 /* AudioConverterView+.swift */, B6BBA90827C39528000FE7D3 /* AudioConverter.swift */, ); path = "Audio Converter"; sourceTree = ""; }; B6C5292527C20DAF0019BCB0 /* ffmpeg */ = { isa = PBXGroup; children = ( B63A034C27C21A99009FD2AD /* ffmpeg */, B6C5292627C20DC60019BCB0 /* FFMpegExecutor.swift */, B63A034F27C24BA0009FD2AD /* FFProgressReport.swift */, B63A035127C25B29009FD2AD /* FFTime.swift */, B6E294B227C3351F00314132 /* FFTask.swift */, B6E294E427C337F300314132 /* FFThumnailGenerator.swift */, ); path = ffmpeg; sourceTree = ""; }; B6CBFEE727CCA1B000902E56 /* big_sur_icon */ = { isa = PBXGroup; children = ( B6CBFEE827CCA1B000902E56 /* squircle_bg_mask_256x256.png */, B6CBFEE927CCA1B000902E56 /* squircle_bg_mask_1024x1024.png */, B6CBFEEA27CCA1B000902E56 /* squircle_back_128x128.png */, B6CBFEEB27CCA1B000902E56 /* squircle_back_1024x1024.png */, B6CBFEEC27CCA1B000902E56 /* squircle_back_16x16.png */, B6CBFEED27CCA1B000902E56 /* squircle_mask_256x256.png */, B6CBFEEE27CCA1B000902E56 /* squircle_mask_16x16.png */, B6CBFEEF27CCA1B000902E56 /* squircle_back_512x512.png */, B6CBFEF027CCA1B000902E56 /* squircle_mask_64x64.png */, B6CBFEF127CCA1B000902E56 /* squircle_mask_128x128.png */, B6CBFEF227CCA1B000902E56 /* squircle_mask_32x32.png */, B6CBFEF327CCA1B000902E56 /* squircle_bg_mask_512x512.png */, B6CBFEF427CCA1B000902E56 /* squircle_mask_1024x1024.png */, B6CBFEF527CCA1B000902E56 /* squircle_back_32x32.png */, B6CBFEF627CCA1B000902E56 /* squircle_back_256x256.png */, B6CBFEF727CCA1B000902E56 /* squircle_back_64x64.png */, B6CBFEF827CCA1B000902E56 /* squircle_mask_512x512.png */, ); path = big_sur_icon; sourceTree = ""; }; B6CBFF0B27CCA88100902E56 /* external_drive */ = { isa = PBXGroup; children = ( B6CBFF1027CCA88E00902E56 /* external_16x16.png */, B6CBFF1127CCA88E00902E56 /* external_32x32.png */, B6CBFF0F27CCA88E00902E56 /* external_64x64.png */, B6CBFF0E27CCA88E00902E56 /* external_128x128.png */, B6CBFF0D27CCA88E00902E56 /* external_256x256.png */, B6CBFF1327CCA88E00902E56 /* external_512x512.png */, B6CBFF1527CCA88F00902E56 /* external_1024x1024.png */, ); path = external_drive; sourceTree = ""; }; B6D1AEDD27A534850022FED2 /* Body */ = { isa = PBXGroup; children = ( B66BE8BD27C49B28003B5ED0 /* Utils */, B6D1AEDE27A534960022FED2 /* BodyViewController.swift */, B6D1AEE327A53A560022FED2 /* HomeView+.swift */, B61796C627BCB6F00054660E /* SettingView+.swift */, B608542127A67FF7003BF243 /* Convert */, B680A79327A68B2D007CB707 /* Coder */, B608542227A68008003BF243 /* Format */, B680A8A827A8E0F8007CB707 /* Generator */, B620141F27A926B400AF5386 /* Text */, B620145427A9440E00AF5386 /* Graphic */, B6BBA90427C391EE000FE7D3 /* Media */, ); path = Body; sourceTree = ""; }; B6D1AEE727A544F20022FED2 /* Component */ = { isa = PBXGroup; children = ( B6D1AEE827A545230022FED2 /* Area.swift */, B6D1AEF327A54C440022FED2 /* Section.swift */, B6D1AEEB27A546810022FED2 /* ControlBackgroundLayer.swift */, B6D1AEF627A54F750022FED2 /* SectionButton.swift */, B608540A27A66694003BF243 /* SectionButton+.swift */, B680A89927A8D9DD007CB707 /* Toast.swift */, B6D1AEF927A557280022FED2 /* PopupButton.swift */, B6D1AEFC27A55ED50022FED2 /* CodeTextView.swift */, B672CF9F27AAB8DD00391A5D /* FileDrop.swift */, B66850ED27A64D3200A3FE01 /* Page.swift */, B608541F27A67E4D003BF243 /* TextField.swift */, B680A83227A8BF7D007CB707 /* TextViewSection.swift */, B680A86727A8D4D1007CB707 /* TextFieldSection.swift */, B62013B527A90B8900AF5386 /* NumberField.swift */, B620141B27A917E000AF5386 /* Button.swift */, B620142227A929B400AF5386 /* TagCloudView.swift */, B6B5726127AC14B60069DBA7 /* TextView.swift */, B6B5726327AC14D10069DBA7 /* RegexTextView.swift */, B673A90127AD0E78005512AB /* DatePicker.swift */, B6113ECC27C4B96C00ACC6E8 /* EmptyImageTableView.swift */, B61157C427C8D77A004D77A5 /* DragImageView.swift */, B6A4F2A327CD078100BBDE7E /* ImageDropView.swift */, ); path = Component; sourceTree = ""; }; B6ED863427BFE12400A17474 /* Exporters */ = { isa = PBXGroup; children = ( B627295127BFB8CB0034D70C /* cwebp */, B627295627BFCCC40034D70C /* HeicImageExporter.swift */, B627294E27BFB7680034D70C /* WebpImageExporter.swift */, B6ED863527BFE7DC00A17474 /* DefaultImageExporter.swift */, ); path = Exporters; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ B64B1E6F27A4F5E200AC2601 /* DevToys */ = { isa = PBXNativeTarget; buildConfigurationList = B64B1E8027A4F5E300AC2601 /* Build configuration list for PBXNativeTarget "DevToys" */; buildPhases = ( B64B1E6C27A4F5E200AC2601 /* Sources */, B64B1E6D27A4F5E200AC2601 /* Frameworks */, B64B1E6E27A4F5E200AC2601 /* Resources */, B608541727A66CC9003BF243 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( B64B1F6027A4FC1F00AC2601 /* PBXTargetDependency */, ); name = DevToys; packageProductDependencies = ( B6D1AF0527A5603B0022FED2 /* Highlightr */, B66850F127A65FC200A3FE01 /* SwiftJSONFormatter */, B608541A27A672E3003BF243 /* Yams */, B680A79727A69268007CB707 /* HTMLEntities */, B680A8AC27A8E31C007CB707 /* CryptoSwift */, B6B5728727ACD2F20069DBA7 /* Kanna */, B6113F0827C4C6C900ACC6E8 /* Sparkle */, B684EA2527C5EFDB0014802F /* DiffMatchPatch */, ); productName = DevToys; productReference = B64B1E7027A4F5E200AC2601 /* DevToys.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ B64B1E6827A4F5E200AC2601 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1240; LastUpgradeCheck = 1240; TargetAttributes = { B64B1E6F27A4F5E200AC2601 = { CreatedOnToolsVersion = 12.4; }; }; }; buildConfigurationList = B64B1E6B27A4F5E200AC2601 /* Build configuration list for PBXProject "DevToys" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ja, "zh-Hans", "pt-BR", de, es, ); mainGroup = B64B1E6727A4F5E200AC2601; packageReferences = ( B6D1AF0427A5603B0022FED2 /* XCRemoteSwiftPackageReference "Highlightr" */, B66850F027A65FC200A3FE01 /* XCRemoteSwiftPackageReference "SwiftJSONFormatter" */, B608541927A672E3003BF243 /* XCRemoteSwiftPackageReference "Yams" */, B680A79627A69268007CB707 /* XCRemoteSwiftPackageReference "swift-html-entities" */, B680A8AB27A8E31C007CB707 /* XCRemoteSwiftPackageReference "CryptoSwift" */, B6B5728627ACD2F20069DBA7 /* XCRemoteSwiftPackageReference "Kanna" */, B6113F0727C4C6C900ACC6E8 /* XCRemoteSwiftPackageReference "Sparkle" */, B684EA2427C5EFDB0014802F /* XCRemoteSwiftPackageReference "DiffMatchPatch" */, ); productRefGroup = B64B1E7127A4F5E200AC2601 /* Products */; projectDirPath = ""; projectReferences = ( { ProductGroup = B64B1F5A27A4FC1800AC2601 /* Products */; ProjectRef = B64B1F5927A4FC1800AC2601 /* CoreUtil.xcodeproj */; }, ); projectRoot = ""; targets = ( B64B1E6F27A4F5E200AC2601 /* DevToys */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ B64B1F5E27A4FC1900AC2601 /* CoreUtil.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = CoreUtil.framework; remoteRef = B64B1F5D27A4FC1900AC2601 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ B64B1E6E27A4F5E200AC2601 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( B69F0E8927CBC2100032F96A /* folder_back_512_bs.png in Resources */, B6E15A9427B6745100DC4D6B /* libimageoptimjpeg.dylib in Resources */, B69F0E9C27CBC7550032F96A /* folder_top_1024.png in Resources */, B69F0E8527CBC2100032F96A /* folder_back_256_bs.png in Resources */, B63A034D27C21A99009FD2AD /* ffmpeg in Resources */, B6BD80F427B8AEE700152AB9 /* Localizable.strings in Resources */, B6CBFF1927CCA88F00902E56 /* external_64x64.png in Resources */, B6CBFF0827CCA1B000902E56 /* squircle_back_64x64.png in Resources */, B69F0E8627CBC2100032F96A /* folder_back_1024_bs.png in Resources */, B6CBFF1D27CCA88F00902E56 /* external_512x512.png in Resources */, B6CBFF0727CCA1B000902E56 /* squircle_back_256x256.png in Resources */, B6CBFEFD27CCA1B000902E56 /* squircle_back_16x16.png in Resources */, B6CBFF1B27CCA88F00902E56 /* external_32x32.png in Resources */, B69F0E9127CBC2340032F96A /* folder_mask2_1024_bs.png in Resources */, B6CBFF0027CCA1B000902E56 /* squircle_back_512x512.png in Resources */, B69F0E9D27CBC7550032F96A /* folder_top_512.png in Resources */, B627295227BFB8CB0034D70C /* cwebp in Resources */, B69F0E8827CBC2100032F96A /* folder_back_16_bs.png in Resources */, B6CBFEFE27CCA1B000902E56 /* squircle_mask_256x256.png in Resources */, B69F0EA627CC7CF40032F96A /* watermark_mask_bs.png in Resources */, B69F0E8727CBC2100032F96A /* folder_back_32_bs.png in Resources */, B69F0EA727CC7CF40032F96A /* watermark_mask_dark_bs.png in Resources */, B69F0E9427CBC2340032F96A /* folder_mask2_32_bs.png in Resources */, B69980D427CCCF0A0063F63D /* android_mask.png in Resources */, B6CBFEFA27CCA1B000902E56 /* squircle_bg_mask_1024x1024.png in Resources */, B69F0E9227CBC2340032F96A /* folder_mask2_512_bs.png in Resources */, B6CBFEFB27CCA1B000902E56 /* squircle_back_128x128.png in Resources */, B6CBFF1727CCA88F00902E56 /* external_256x256.png in Resources */, B6CBFF0627CCA1B000902E56 /* squircle_back_32x32.png in Resources */, B6CBFEFF27CCA1B000902E56 /* squircle_mask_16x16.png in Resources */, B6CBFF0527CCA1B000902E56 /* squircle_mask_1024x1024.png in Resources */, B6CBFF0927CCA1B000902E56 /* squircle_mask_512x512.png in Resources */, B6AC276527AA3D61000FD713 /* jpegoptim in Resources */, B6CBFF0427CCA1B000902E56 /* squircle_bg_mask_512x512.png in Resources */, B69F0E9327CBC2340032F96A /* folder_mask2_16_bs.png in Resources */, B6CBFF0327CCA1B000902E56 /* squircle_mask_32x32.png in Resources */, B6CBFF0127CCA1B000902E56 /* squircle_mask_64x64.png in Resources */, B6AC273027AA1373000FD713 /* optipng in Resources */, B6CBFF1827CCA88F00902E56 /* external_128x128.png in Resources */, B6CBFF1F27CCA88F00902E56 /* external_1024x1024.png in Resources */, B6CBFEFC27CCA1B000902E56 /* squircle_back_1024x1024.png in Resources */, B69F0E9627CBC2340032F96A /* folder_mask2_128_bs.png in Resources */, B6CBFF1A27CCA88F00902E56 /* external_16x16.png in Resources */, B64B1E7827A4F5E300AC2601 /* Assets.xcassets in Resources */, B67A4C5727C0EA84009277FB /* ACOverlayController.xib in Resources */, B69F0E8427CBC2100032F96A /* folder_back_128_bs.png in Resources */, B6CBFEF927CCA1B000902E56 /* squircle_bg_mask_256x256.png in Resources */, B64B1E7B27A4F5E300AC2601 /* Main.storyboard in Resources */, B69F0E8327CBC2100032F96A /* folder_back_64_bs.png in Resources */, B6CBFF0227CCA1B000902E56 /* squircle_mask_128x128.png in Resources */, B69F0E9827CBC2800032F96A /* folder_mask2_256_bs.png in Resources */, B69F0E9527CBC2340032F96A /* folder_mask2_64_bs.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ B64B1E6C27A4F5E200AC2601 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( B62013B627A90B8900AF5386 /* NumberField.swift in Sources */, B6BD80EB27B89EA600152AB9 /* ImageOptimaizerView.swift in Sources */, B684EA2A27C609620014802F /* QRCodeGeneratorView+.swift in Sources */, B67A4C0D27C0BA00009277FB /* Color.swift in Sources */, B608541E27A67B48003BF243 /* NumberBaseConverterView+.swift in Sources */, B680A8AA27A8E106007CB707 /* HashGeneratorView+.swift in Sources */, B6A4F2A427CD078100BBDE7E /* ImageDropView.swift in Sources */, B6789EBC27CA48A300D8B58C /* IosIconGenerator.swift in Sources */, B67A4CC327C0F4D4009277FB /* CircleHueBarView.swift in Sources */, B67A4CC927C10493009277FB /* SaturationBarView.swift in Sources */, B64B202027A523A200AC2601 /* ToolMenuCell.swift in Sources */, B64AA45027AD0D3900EC436D /* DateConverterView+.swift in Sources */, B6E294E527C337F300314132 /* FFThumnailGenerator.swift in Sources */, B6C5292727C20DC60019BCB0 /* FFMpegExecutor.swift in Sources */, B672CFA027AAB8DD00391A5D /* FileDrop.swift in Sources */, B608540B27A66694003BF243 /* SectionButton+.swift in Sources */, B67A4C4E27C0EA84009277FB /* ACOverlayPreview.swift in Sources */, B63A034B27C2130A009FD2AD /* GifConverter.swift in Sources */, B67A4C0C27C0BA00009277FB /* ColorBoxView.swift in Sources */, B6B5726427AC14D10069DBA7 /* RegexTextView.swift in Sources */, B67A4C5227C0EA84009277FB /* ShowAndHideCursor.m in Sources */, B6A93D3227CBBC97003A6D7F /* IconTemplete+.swift in Sources */, B69F0E9F27CC76A50032F96A /* IconImageManager.swift in Sources */, B6789EBA27CA43E200D8B58C /* IconGeneratorView+.swift in Sources */, B680A83327A8BF7D007CB707 /* TextViewSection.swift in Sources */, B6B5726227AC14B60069DBA7 /* TextView.swift in Sources */, B6C5292427C20D520019BCB0 /* GifConverterView+.swift in Sources */, B66BE8BC27C49AD4003B5ED0 /* AudioFileScanner.swift in Sources */, B627295727BFCCC40034D70C /* HeicImageExporter.swift in Sources */, B67A4CC727C0F546009277FB /* CircleBoxHSBColorPicker.swift in Sources */, B67A4C5827C0EA84009277FB /* ACOverlayWrapper.swift in Sources */, B67A4C5427C0EA84009277FB /* Ex+CGImage.swift in Sources */, B620142327A929B400AF5386 /* TagCloudView.swift in Sources */, B67A4C5627C0EA84009277FB /* ACOverlayController.swift in Sources */, B6113ECF27C4BF2300ACC6E8 /* FileConflictAvoider.swift in Sources */, B67A4C0927C0BA00009277FB /* OpacityBarView.swift in Sources */, B6B5728027ACC3AF0069DBA7 /* ChecksumGeneratorView+.swift in Sources */, B64B209827A532DF00AC2601 /* AppWindowController.swift in Sources */, B67A4CC027C0F4BA009277FB /* BoxHSBColorPicker.swift in Sources */, B673A90227AD0E78005512AB /* DatePicker.swift in Sources */, B620141C27A917E000AF5386 /* Button.swift in Sources */, B66536CE27C9BA4000CA1CEC /* Slugify.swift in Sources */, B6AC273227AA2BF6000FD713 /* ImageOptimizer.swift in Sources */, B6789EDF27CA5F5D00D8B58C /* IconsetGenerator.swift in Sources */, B6789EE727CB334D00D8B58C /* AndroidIconGenerator.swift in Sources */, B67A4C0A27C0BA00009277FB /* HueBarView.swift in Sources */, B6113F0127C4BF7700ACC6E8 /* Identifier.swift in Sources */, B65DB78027AC0EB400146A3C /* RegexTesterView+.swift in Sources */, B6B5728F27ACFA6E0069DBA7 /* ImageDropper.swift in Sources */, B680A89A27A8D9DD007CB707 /* Toast.swift in Sources */, B6A4F2A227CD05F000BBDE7E /* QRCodeReaderView+.swift in Sources */, B6D1AEF727A54F750022FED2 /* SectionButton.swift in Sources */, B684EA2827C5EFE90014802F /* TextDiffView+.swift in Sources */, B6789EE127CA5F6900D8B58C /* IcnsGenerator.swift in Sources */, B63A035027C24BA0009FD2AD /* FFProgressReport.swift in Sources */, B6A93D3327CBBC97003A6D7F /* IconSet.swift in Sources */, B6D88A8B27D07204002E71EA /* CameraViewController.swift in Sources */, B64B1E7627A4F5E200AC2601 /* AppViewController.swift in Sources */, B67A4C5327C0EA84009277FB /* Ex+NSColor.swift in Sources */, B6E294B327C3351F00314132 /* FFTask.swift in Sources */, B6D1AEE927A545230022FED2 /* Area.swift in Sources */, B64B1F7727A4FF8B00AC2601 /* SidebarView+.swift in Sources */, B64B202327A52A2D00AC2601 /* R.swift in Sources */, B6B5728227ACCF2F0069DBA7 /* XMLFormatterView+.swift in Sources */, B67A4C4F27C0EA84009277FB /* Ex+NSBezierPath.swift in Sources */, B64B201D27A5219400AC2601 /* SearchCell.swift in Sources */, B6BD80EF27B8A85400152AB9 /* ToolCategory+Default.swift in Sources */, B67A4C0827C0BA00009277FB /* ColorPickerHandleLayer.swift in Sources */, B66850EE27A64D3200A3FE01 /* Page.swift in Sources */, B61796C527BCB3090054660E /* ToolCategoryCell.swift in Sources */, B6BD80F127B8A86100152AB9 /* Tool+Default.swift in Sources */, B6D1AEF427A54C440022FED2 /* Section.swift in Sources */, B64B1F7227A4FDC800AC2601 /* AppModel.swift in Sources */, B6D1AEFA27A557280022FED2 /* PopupButton.swift in Sources */, B69980D727CCE09A0063F63D /* IcoGenerator.swift in Sources */, B6789EE327CA5F7A00D8B58C /* IconFolderGenerator.swift in Sources */, B6B5728A27ACDA420069DBA7 /* ImageConverterView+.swift in Sources */, B67A4CCD27C12C09009277FB /* CircleBarsHSBColorPicker.swift in Sources */, B69980D927CCEB320063F63D /* PngIconGenerator.swift in Sources */, B6D1AEF027A547B90022FED2 /* JSONFormatterView+.swift in Sources */, B64844FF27B767EC004FE02B /* ToolManager+.swift in Sources */, B620142127A9271500AF5386 /* TextInspectorView+.swift in Sources */, B64B1E7427A4F5E200AC2601 /* AppDelegate.swift in Sources */, B620141E27A91F6700AF5386 /* LoremIpsumGeneratorView+.swift in Sources */, B627294F27BFB7680034D70C /* WebpImageExporter.swift in Sources */, B61157C027C8C3F9004D77A5 /* JSONSearchView+.swift in Sources */, B6113ECD27C4B96C00ACC6E8 /* EmptyImageTableView.swift in Sources */, B6B5728C27ACE5600069DBA7 /* ImageConverter.swift in Sources */, B67A4C5027C0EA84009277FB /* Util+PixelPicker.swift in Sources */, B6789EE527CA5F9500D8B58C /* IconGenerator+Model.swift in Sources */, B6D1AEDF27A534960022FED2 /* BodyViewController.swift in Sources */, B67A4C5127C0EA84009277FB /* ShowAndHideCursor.swift in Sources */, B61157C527C8D77A004D77A5 /* DragImageView.swift in Sources */, B66536D027C9C56600CA1CEC /* SQLFormatterView+.swift in Sources */, B680A79C27A6947D007CB707 /* Base64DecoderView+.swift in Sources */, B67A4CC527C0F50D009277FB /* R+ColorPicker.swift in Sources */, B680A86827A8D4D1007CB707 /* TextFieldSection.swift in Sources */, B67A4CBE27C0EEEF009277FB /* ColorSampleView.swift in Sources */, B680A79527A68B35007CB707 /* HTMLDecoderView+.swift in Sources */, B67A4CCB27C10548009277FB /* BrightnessBarView.swift in Sources */, B680A8A727A8DD5D007CB707 /* JWTDecoderView+.swift in Sources */, 6B42318827AD1BC0002D135A /* HyphenationRemoverView+.swift in Sources */, B6BBA90927C39528000FE7D3 /* AudioConverter.swift in Sources */, B6BBA90727C392E3000FE7D3 /* AudioConverterView+.swift in Sources */, B61796C727BCB6F00054660E /* SettingView+.swift in Sources */, B6D1AEE427A53A560022FED2 /* HomeView+.swift in Sources */, B6AC276A27AA535A000FD713 /* PDFGeneratorView+.swift in Sources */, B608542027A67E4D003BF243 /* TextField.swift in Sources */, B6A93D3427CBBC97003A6D7F /* IconTemplete.swift in Sources */, B67A4C5527C0EA84009277FB /* ACOverlayPanel.swift in Sources */, B680A79A27A69324007CB707 /* URLDecoderView+.swift in Sources */, B61157C327C8C78A004D77A5 /* JSONNormalSearchView.swift in Sources */, B63A035227C25B29009FD2AD /* FFTime.swift in Sources */, B6ED863627BFE7DC00A17474 /* DefaultImageExporter.swift in Sources */, B61796C927BCB84A0054660E /* Settings.swift in Sources */, B67A4C0B27C0BA00009277FB /* ColorPickerView+.swift in Sources */, B608541227A66A90003BF243 /* JSONYamlConverterView+.swift in Sources */, B6D1AEFD27A55ED50022FED2 /* CodeTextView.swift in Sources */, B62013B427A9070D00AF5386 /* UUIDGeneratorView+.swift in Sources */, B6D1AEEC27A546810022FED2 /* ControlBackgroundLayer.swift in Sources */, B67A4C4D27C0EA84009277FB /* ACPixelPicker.swift in Sources */, B64B201627A5100800AC2601 /* Separator.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ B64B1F6027A4FC1F00AC2601 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = CoreUtil; targetProxy = B64B1F5F27A4FC1F00AC2601 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ B64B1E7927A4F5E300AC2601 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( B64B1E7A27A4F5E300AC2601 /* Base */, B67A4CCE27C1E010009277FB /* zh-Hans */, B67A4CD027C1E036009277FB /* pt-BR */, B67A4CD427C1E0DC009277FB /* en */, B67A4CD527C1E0DF009277FB /* ja */, B67A4CD727C1E0F1009277FB /* de */, B67A4CD927C1E469009277FB /* es */, ); name = Main.storyboard; sourceTree = ""; }; B6BD80F627B8AEE700152AB9 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( B6BD80F527B8AEE700152AB9 /* en */, B6BD80F827B8AF1800152AB9 /* ja */, B67A4CCF27C1E01C009277FB /* zh-Hans */, B67A4CD127C1E03C009277FB /* pt-BR */, B67A4CD827C1E0F8009277FB /* de */, B67A4CDA27C1E46A009277FB /* es */, ); name = Localizable.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ B64B1E7E27A4F5E300AC2601 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; B64B1E7F27A4F5E300AC2601 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; B64B1E8127A4F5E300AC2601 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = DevToys/DevToys.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 0.0.10; DEVELOPMENT_TEAM = T5HMUWWK5R; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = DevToys/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/DevToys/Body/Media/Image\\ Optimizer", "$(PROJECT_DIR)/DevToys/Body/Media/Image\\ Optimizer", "$(PROJECT_DIR)/DevToys/Body/Media/Image\\ Optimizer", ); MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 0.0.10; PRODUCT_BUNDLE_IDENTIFIER = com.yuki.DevToys; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ENFORCE_EXCLUSIVE_ACCESS = off; SWIFT_OBJC_BRIDGING_HEADER = "DevToys/Bridging-Header.h"; SWIFT_VERSION = 5.0; }; name = Debug; }; B64B1E8227A4F5E300AC2601 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = DevToys/DevToys.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 0.0.10; DEVELOPMENT_TEAM = T5HMUWWK5R; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = DevToys/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/DevToys/Body/Media/Image\\ Optimizer", "$(PROJECT_DIR)/DevToys/Body/Media/Image\\ Optimizer", "$(PROJECT_DIR)/DevToys/Body/Media/Image\\ Optimizer", ); MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 0.0.10; PRODUCT_BUNDLE_IDENTIFIER = com.yuki.DevToys; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ENFORCE_EXCLUSIVE_ACCESS = off; SWIFT_OBJC_BRIDGING_HEADER = "DevToys/Bridging-Header.h"; SWIFT_VERSION = 5.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ B64B1E6B27A4F5E200AC2601 /* Build configuration list for PBXProject "DevToys" */ = { isa = XCConfigurationList; buildConfigurations = ( B64B1E7E27A4F5E300AC2601 /* Debug */, B64B1E7F27A4F5E300AC2601 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; B64B1E8027A4F5E300AC2601 /* Build configuration list for PBXNativeTarget "DevToys" */ = { isa = XCConfigurationList; buildConfigurations = ( B64B1E8127A4F5E300AC2601 /* Debug */, B64B1E8227A4F5E300AC2601 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ B608541927A672E3003BF243 /* XCRemoteSwiftPackageReference "Yams" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ObuchiYuki/Yams"; requirement = { branch = main; kind = branch; }; }; B6113F0727C4C6C900ACC6E8 /* XCRemoteSwiftPackageReference "Sparkle" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/sparkle-project/Sparkle"; requirement = { kind = upToNextMajorVersion; minimumVersion = 2.0.0; }; }; B66850F027A65FC200A3FE01 /* XCRemoteSwiftPackageReference "SwiftJSONFormatter" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ObuchiYuki/SwiftJSONFormatter"; requirement = { branch = main; kind = branch; }; }; B680A79627A69268007CB707 /* XCRemoteSwiftPackageReference "swift-html-entities" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Kitura/swift-html-entities.git"; requirement = { kind = upToNextMajorVersion; minimumVersion = 4.0.0; }; }; B680A8AB27A8E31C007CB707 /* XCRemoteSwiftPackageReference "CryptoSwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/krzyzanowskim/CryptoSwift.git"; requirement = { kind = upToNextMajorVersion; minimumVersion = 1.4.2; }; }; B684EA2427C5EFDB0014802F /* XCRemoteSwiftPackageReference "DiffMatchPatch" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ObuchiYuki/DiffMatchPatch"; requirement = { kind = upToNextMajorVersion; minimumVersion = 1.0.0; }; }; B6B5728627ACD2F20069DBA7 /* XCRemoteSwiftPackageReference "Kanna" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/tid-kijyun/Kanna.git"; requirement = { kind = upToNextMajorVersion; minimumVersion = 5.2.7; }; }; B6D1AF0427A5603B0022FED2 /* XCRemoteSwiftPackageReference "Highlightr" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/raspu/Highlightr"; requirement = { kind = upToNextMajorVersion; minimumVersion = 2.1.2; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ B608541A27A672E3003BF243 /* Yams */ = { isa = XCSwiftPackageProductDependency; package = B608541927A672E3003BF243 /* XCRemoteSwiftPackageReference "Yams" */; productName = Yams; }; B6113F0827C4C6C900ACC6E8 /* Sparkle */ = { isa = XCSwiftPackageProductDependency; package = B6113F0727C4C6C900ACC6E8 /* XCRemoteSwiftPackageReference "Sparkle" */; productName = Sparkle; }; B66850F127A65FC200A3FE01 /* SwiftJSONFormatter */ = { isa = XCSwiftPackageProductDependency; package = B66850F027A65FC200A3FE01 /* XCRemoteSwiftPackageReference "SwiftJSONFormatter" */; productName = SwiftJSONFormatter; }; B680A79727A69268007CB707 /* HTMLEntities */ = { isa = XCSwiftPackageProductDependency; package = B680A79627A69268007CB707 /* XCRemoteSwiftPackageReference "swift-html-entities" */; productName = HTMLEntities; }; B680A8AC27A8E31C007CB707 /* CryptoSwift */ = { isa = XCSwiftPackageProductDependency; package = B680A8AB27A8E31C007CB707 /* XCRemoteSwiftPackageReference "CryptoSwift" */; productName = CryptoSwift; }; B684EA2527C5EFDB0014802F /* DiffMatchPatch */ = { isa = XCSwiftPackageProductDependency; package = B684EA2427C5EFDB0014802F /* XCRemoteSwiftPackageReference "DiffMatchPatch" */; productName = DiffMatchPatch; }; B6B5728727ACD2F20069DBA7 /* Kanna */ = { isa = XCSwiftPackageProductDependency; package = B6B5728627ACD2F20069DBA7 /* XCRemoteSwiftPackageReference "Kanna" */; productName = Kanna; }; B6D1AF0527A5603B0022FED2 /* Highlightr */ = { isa = XCSwiftPackageProductDependency; package = B6D1AF0427A5603B0022FED2 /* XCRemoteSwiftPackageReference "Highlightr" */; productName = Highlightr; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = B64B1E6827A4F5E200AC2601 /* Project object */; } ================================================ FILE: DevToys/DevToys.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: DevToys/DevToys.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: DevToys/DevToys.xcodeproj/xcshareddata/xcschemes/DevToys.xcscheme ================================================ ================================================ FILE: DevToys.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: DevToys.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: DevToys.xcworkspace/xcshareddata/swiftpm/Package.resolved ================================================ { "object": { "pins": [ { "package": "CryptoSwift", "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git", "state": { "branch": null, "revision": "12f2389aca4a07e0dd54c86ec23d0721ed88b8db", "version": "1.4.3" } }, { "package": "DiffMatchPatch", "repositoryURL": "https://github.com/ObuchiYuki/DiffMatchPatch", "state": { "branch": null, "revision": "5db22e8f9ac588a130f62733105bbe5c5bdd7bc3", "version": "1.0.3" } }, { "package": "Highlightr", "repositoryURL": "https://github.com/raspu/Highlightr", "state": { "branch": null, "revision": "93199b9e434f04bda956a613af8f571933f9f037", "version": "2.1.2" } }, { "package": "Kanna", "repositoryURL": "https://github.com/tid-kijyun/Kanna.git", "state": { "branch": null, "revision": "f9e4922223dd0d3dfbf02ca70812cf5531fc0593", "version": "5.2.7" } }, { "package": "Promise", "repositoryURL": "https://github.com/ObuchiYuki/Promise.git", "state": { "branch": null, "revision": "cf504f07706a11f9cf2af339e9b1ffca2b95d4ac", "version": "1.0.13" } }, { "package": "SnapKit", "repositoryURL": "https://github.com/SnapKit/SnapKit.git", "state": { "branch": null, "revision": "d458564516e5676af9c70b4f4b2a9178294f1bc6", "version": "5.0.1" } }, { "package": "Sparkle", "repositoryURL": "https://github.com/sparkle-project/Sparkle", "state": { "branch": null, "revision": "286edd1fa22505a9e54d170e9fd07d775ea233f2", "version": "2.1.0" } }, { "package": "swift-collections", "repositoryURL": "https://github.com/apple/swift-collections", "state": { "branch": null, "revision": "48254824bb4248676bf7ce56014ff57b142b77eb", "version": "1.0.2" } }, { "package": "HTMLEntities", "repositoryURL": "https://github.com/Kitura/swift-html-entities.git", "state": { "branch": null, "revision": "3686a2b931be24dd2531307f89e2f1d648c0200e", "version": "4.0.0" } }, { "package": "SwiftJSONFormatter", "repositoryURL": "https://github.com/ObuchiYuki/SwiftJSONFormatter", "state": { "branch": "main", "revision": "65191c3708b2b74e43cac716d14deeb2536865a8", "version": null } }, { "package": "Yams", "repositoryURL": "https://github.com/ObuchiYuki/Yams", "state": { "branch": "main", "revision": "78957e5f5c026b3a4f2f54498cf18ff3bf42fdf8", "version": null } } ] }, "version": 1 } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2022 ObuchiYuki Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # DevToysMac This is the mac app version of [DevToys for Windows](https://github.com/veler/DevToys)! ![Dribbble Shot](https://user-images.githubusercontent.com/20896810/154781951-f4c6fa80-2fcc-40fe-a94b-fccfc0f2ccf1.png) # How to install ## Manually - Download the [latest release](https://github.com/ObuchiYuki/DevToysMac/releases/latest). - Extract `DevToys.app` from `DevToys.app.zip` ## Homebrew - Install [Homebrew](https://brew.sh/). Then install DevToysMac with `brew install --cask devtoys`. # Screenshots ### Home スクリーンショット 2022-01-30 19 01 01 ### Json <> Yaml Converter スクリーンショット 2022-01-30 19 01 23 ### Number Base Converter スクリーンショット 2022-01-30 19 01 41 ### HTML Encoder / Decoder スクリーンショット 2022-01-30 19 02 05 ### URL Encoder / Decoder スクリーンショット 2022-01-30 19 02 11 ### Base64 Encoder / Decoder スクリーンショット 2022-01-30 19 02 49 ### JSON Formatter スクリーンショット 2022-01-30 19 04 43 and more... ================================================ FILE: Release Checklist.md ================================================ # Release Checklist This is the procedure for doing a release. 1. Update the project version from Xcode's project pane Update both Version and Build (Sparkle seems to be referring to the build.) 2. Archive and Notarize app - With Distribute App > Developer ID 3. Create a tag for the release and push that tag. 4. Create a Github release page. 5. Create a zip file of the nolarized app on **local** (not the github page) and upload it to Asset. - The checksum of the zip file will be requested by appcast. 6. Generate appcast file with `generate_appcast` command. 7. Publish release on GitHub. 8. Update generated `appcast.xml` 's `` to release asset URL. 9. Add new `appcast.xml` to git and push. 10. If needed, create `release-note.html` for Sparkle update. ================================================ FILE: docs/index.html ================================================ Document

Hello World

================================================ FILE: docs/sparkle/appcast.xml ================================================ DevToys 0.0.10 Sat, 26 Feb 2022 15:33:30 +0900 0.0.10 0.0.10 10.15