Skip to content

Commit

Permalink
add date converter
Browse files Browse the repository at this point in the history
  • Loading branch information
ObuchiYuki committed Feb 4, 2022
1 parent a65d40a commit 8cb7b45
Show file tree
Hide file tree
Showing 20 changed files with 784 additions and 35 deletions.
9 changes: 9 additions & 0 deletions DevToys.xcworkspace/xcshareddata/swiftpm/Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
"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",
Expand Down
57 changes: 53 additions & 4 deletions DevToys/DevToys.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions DevToys/DevToys/Body/BodyViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ final class BodyViewController: NSViewController {
private lazy var networkInfomationController = NetworkInfomationViewController()
private lazy var regexTesterController = RegexTesterViewController()
private lazy var checksumGeneratorController = ChecksumGeneratorViewController()
private lazy var xmlFormatterController = XMLFormatterViewController()
private lazy var imageConverterController = ImageConverterViewController()
private lazy var dateConverterController = DateConverterViewController()

private lazy var notImplementedController = NotImplementedViewController()

Expand Down Expand Up @@ -56,6 +59,9 @@ final class BodyViewController: NSViewController {
case .networkInfomation: self.replaceContentViewController(networkInfomationController)
case .regexTester: self.replaceContentViewController(regexTesterController)
case .checksumGenerator: self.replaceContentViewController(checksumGeneratorController)
case .xmlFormatter: self.replaceContentViewController(xmlFormatterController)
case .imageConverter: self.replaceContentViewController(imageConverterController)
case .dateConvertor: self.replaceContentViewController(dateConverterController)
default: self.replaceContentViewController(notImplementedController)
}
}
Expand Down
80 changes: 80 additions & 0 deletions DevToys/DevToys/Body/Convert/DateConverter+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// DateConverter.swift
// DevToys
//
// Created by yuki on 2022/02/04.
//

import CoreUtil

final class DateConverterViewController: PageViewController {
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 graphicDatePicker = NSDatePicker()

override func onAwake() {
self.title = "Date Converter"

self.addSection(Section(title: "Date", 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", items: [unixTimeField]))
self.unixTimeField.showStepper = false
self.unixTimeField.snp.remakeConstraints{ make in
make.height.equalTo(R.Size.controlHeight)
}
self.addSection(Section(title: "ISO 8601", items: [isoDateField]))

self.addSection(Section(title: "Calender", items: [graphicDatePicker]))
self.graphicDatePicker.datePickerStyle = .clockAndCalendar
}
}

113 changes: 113 additions & 0 deletions DevToys/DevToys/Body/Format/XMLFormatter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//
// XMLFormatter.swift
// DevToys
//
// Created by yuki on 2022/02/04.
//

import CoreUtil

final class XMLFormatterViewController: PageViewController {
@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: #"<?xml version="1.0" encoding="UTF-8"?>"#, with: "")
.replacingOccurrences(of: #"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">"#, with: "")
.replacingOccurrences(of: #"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">"#, with: "")
.replacingOccurrences(of: #"<html xmlns="http://www.w3.org/1999/xhtml">"#, with: "<html>")
.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 }
}

final private class XMLFormatterView: Page {
let prettySwitch = NSSwitch()
let autoFixSwitch = NSSwitch()
let documentTypeControl = EnumPopupButton<DocumentType>()
let inputSection = CodeViewSection(title: "Input", options: .defaultInput, language: .xml)
let outputSection = CodeViewSection(title: "Output", options: .defaultOutput, language: .xml)

private lazy var configurationSection = Section(title: "Configuration", items: [
Area(icon: R.Image.convert, title: "Document Type", control: documentTypeControl),
NSStackView() => {
$0.orientation = .horizontal
$0.distribution = .fillEqually
$0.addArrangedSubview(Area(icon: R.Image.format, title: "Auto Fix Document", control: autoFixSwitch))
$0.addArrangedSubview(Area(icon: R.Image.format, title: "Pretty Document", 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 - 160))
}
}

override func onAwake() {
self.title = "XML Formatter"
self.addSection(configurationSection)
}
}
123 changes: 123 additions & 0 deletions DevToys/DevToys/Body/Media/Image Converter/ImageConverter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//
// ImageConverter.swift
// DevToys
//
// Created by yuki on 2022/02/04.
//

import CoreUtil

struct ImageConvertTask {
let image: NSImage
let title: String
let size: CGSize
let isDone: Promise<Void, Error>
}

enum ImageConverter {
private static let destinationDirectory = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask)[0].appendingPathComponent("Converted Images") => {
try? FileManager.default.createDirectory(at: $0, withIntermediateDirectories: true, attributes: nil)
}

static func convert(_ item: ImageItem, format: ImageFormatType, resize: Bool, size: CGSize, scale: ImageScaleMode, padding: Bool) -> ImageConvertTask {
var resizeSize = item.image.size
if resize {
// switch scale {
// case <#pattern#>:
// <#code#>
// default:
// <#code#>
// }
}

// item.image.resized(to: <#T##NSSize#>)
let isDone = Promise<Data, Error>.asyncError{ resolve, reject in
switch format {
case .png:
guard let data = item.image.png else { return reject("Data failed.") }; resolve(data)
case .jpg:
guard let data = item.image.jpeg else { return reject("Data failed.") }; resolve(data)
case .tiff:
guard let data = item.image.tiffRepresentation else { return reject("Data failed.") }; resolve(data)
case .gif:
guard let data = item.image.gif else { return reject("Data failed.") }; resolve(data)
}
}
.tryPeek{ data in
let url = destinationDirectory.appendingPathComponent("\(item.title).\(format.exp)")
try data.write(to: url)
}
.receive(on: .main)
.eraseToVoid()

return ImageConvertTask(image: item.image, title: item.title, size: item.image.size, isDone: isDone)
}
}

extension ImageFormatType {
var exp: String {
switch self {
case .png: return "png"
case .jpg: return "jpg"
case .gif: return "gif"
case .tiff: return "tiff"
}
}
}


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) }
}

extension Promise {
public func tryPeek(_ receiveOutput: @escaping (Output) throws -> ()) -> Promise<Output, Error> {
Promise<Output, Error> { resolve, reject in
self.sink({ output in
do { try receiveOutput(output); resolve(output) } catch { reject(error) }
}, reject)
}
}
}

extension NSImage {
var pngData: Data { png! }

func resized(to newSize: NSSize) -> NSImage {
if let bitmapRep = NSBitmapImageRep(
bitmapDataPlanes: nil, pixelsWide: Int(newSize.width), pixelsHigh: Int(newSize.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false,
colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 0
) {
bitmapRep.size = newSize
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: bitmapRep)
draw(in: NSRect(x: 0, y: 0, width: newSize.width, height: newSize.height), from: .zero, operation: .copy, fraction: 1.0)
NSGraphicsContext.restoreGraphicsState()

let resizedImage = NSImage(size: newSize)
resizedImage.addRepresentation(bitmapRep)
return resizedImage
}

fatalError()
}
}
Loading

0 comments on commit 8cb7b45

Please sign in to comment.