Skip to content
This repository was archived by the owner on Jun 11, 2024. It is now read-only.

Commit 8cb7b45

Browse files
committed
add date converter
1 parent a65d40a commit 8cb7b45

File tree

20 files changed

+784
-35
lines changed

20 files changed

+784
-35
lines changed

DevToys.xcworkspace/xcshareddata/swiftpm/Package.resolved

+9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@
1919
"version": "2.1.2"
2020
}
2121
},
22+
{
23+
"package": "Kanna",
24+
"repositoryURL": "https://github.com/tid-kijyun/Kanna.git",
25+
"state": {
26+
"branch": null,
27+
"revision": "f9e4922223dd0d3dfbf02ca70812cf5531fc0593",
28+
"version": "5.2.7"
29+
}
30+
},
2231
{
2332
"package": "Promise",
2433
"repositoryURL": "https://github.com/ObuchiYuki/Promise.git",

DevToys/DevToys.xcodeproj/project.pbxproj

+53-4
Large diffs are not rendered by default.

DevToys/DevToys/Body/BodyViewController.swift

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ final class BodyViewController: NSViewController {
2828
private lazy var networkInfomationController = NetworkInfomationViewController()
2929
private lazy var regexTesterController = RegexTesterViewController()
3030
private lazy var checksumGeneratorController = ChecksumGeneratorViewController()
31+
private lazy var xmlFormatterController = XMLFormatterViewController()
32+
private lazy var imageConverterController = ImageConverterViewController()
33+
private lazy var dateConverterController = DateConverterViewController()
3134

3235
private lazy var notImplementedController = NotImplementedViewController()
3336

@@ -56,6 +59,9 @@ final class BodyViewController: NSViewController {
5659
case .networkInfomation: self.replaceContentViewController(networkInfomationController)
5760
case .regexTester: self.replaceContentViewController(regexTesterController)
5861
case .checksumGenerator: self.replaceContentViewController(checksumGeneratorController)
62+
case .xmlFormatter: self.replaceContentViewController(xmlFormatterController)
63+
case .imageConverter: self.replaceContentViewController(imageConverterController)
64+
case .dateConvertor: self.replaceContentViewController(dateConverterController)
5965
default: self.replaceContentViewController(notImplementedController)
6066
}
6167
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// DateConverter.swift
3+
// DevToys
4+
//
5+
// Created by yuki on 2022/02/04.
6+
//
7+
8+
import CoreUtil
9+
10+
final class DateConverterViewController: PageViewController {
11+
private let cell = DateConverterView()
12+
13+
@Observable var date = Date()
14+
15+
private let isoFormatter = ISO8601DateFormatter()
16+
private let gmtFormatter = DateFormatter() => {
17+
$0.locale = Locale(identifier: "en_US_POSIX")
18+
$0.timeZone = TimeZone(abbreviation: "GMT")
19+
}
20+
21+
override func loadView() { self.view = cell }
22+
23+
override func viewDidLoad() {
24+
self.$date
25+
.sink{[unowned self] in
26+
self.cell.datePicker.date = $0
27+
self.cell.unixTimeField.value = $0.timeIntervalSince1970.rounded()
28+
self.cell.isoDateField.string = isoFormatter.string(from: $0)
29+
self.cell.graphicDatePicker.dateValue = $0
30+
}
31+
.store(in: &objectBag)
32+
33+
self.cell.graphicDatePicker.actionPublisher
34+
.sink{[unowned self] in self.date = cell.graphicDatePicker.dateValue }.store(in: &objectBag)
35+
self.cell.nowButton.actionPublisher
36+
.sink{[unowned self] in self.date = Date() }.store(in: &objectBag)
37+
self.cell.datePicker.datePublisher
38+
.sink{[unowned self] in self.date = $0 }.store(in: &objectBag)
39+
self.cell.unixTimeField.valuePublisher
40+
.sink{[unowned self] in self.date = Date(timeIntervalSince1970: $0.reduce(self.date.timeIntervalSince1970)) }.store(in: &objectBag)
41+
self.cell.isoDateField.changeStringPublisher.compactMap{[unowned self] in isoFormatter.date(from: $0) }
42+
.sink{[unowned self] in self.date = $0 }.store(in: &objectBag)
43+
}
44+
}
45+
46+
final private class DateConverterView: Page {
47+
48+
let datePicker = DatePicker()
49+
let utcDatePicker = DatePicker()
50+
let nowButton = Button(title: "Now")
51+
let unixTimeField = NumberField()
52+
let isoDateField = TextField(showCopyButton: false)
53+
let graphicDatePicker = NSDatePicker()
54+
55+
override func onAwake() {
56+
self.title = "Date Converter"
57+
58+
self.addSection(Section(title: "Date", items: [
59+
NSStackView() => {
60+
$0.distribution = .equalSpacing
61+
$0.addArrangedSubview(datePicker)
62+
$0.addArrangedSubview(nowButton)
63+
}
64+
]))
65+
self.datePicker.snp.remakeConstraints{ make in
66+
make.right.equalTo(nowButton.snp.left).inset(-8)
67+
}
68+
69+
self.addSection(Section(title: "Unix Time", items: [unixTimeField]))
70+
self.unixTimeField.showStepper = false
71+
self.unixTimeField.snp.remakeConstraints{ make in
72+
make.height.equalTo(R.Size.controlHeight)
73+
}
74+
self.addSection(Section(title: "ISO 8601", items: [isoDateField]))
75+
76+
self.addSection(Section(title: "Calender", items: [graphicDatePicker]))
77+
self.graphicDatePicker.datePickerStyle = .clockAndCalendar
78+
}
79+
}
80+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//
2+
// XMLFormatter.swift
3+
// DevToys
4+
//
5+
// Created by yuki on 2022/02/04.
6+
//
7+
8+
import CoreUtil
9+
10+
final class XMLFormatterViewController: PageViewController {
11+
@RestorableState("xml.rawCode") private var rawCode: String = ""
12+
@RestorableState("xml.formattedCode") private var formattedCode: String = ""
13+
@RestorableState("xml.documentType") private var documentType: DocumentType = .xmlDocument
14+
@RestorableState("xml.pretty") private var pretty = true
15+
@RestorableState("xml.autofix") private var autofix = true
16+
17+
private let cell = XMLFormatterView()
18+
19+
override func loadView() { self.view = cell }
20+
21+
override func viewDidLoad() {
22+
self.$rawCode
23+
.sink{[unowned self] in cell.inputSection.string = $0 }.store(in: &objectBag)
24+
self.$formattedCode
25+
.sink{[unowned self] in cell.outputSection.string = $0 }.store(in: &objectBag)
26+
self.$documentType
27+
.sink{[unowned self] in cell.documentTypeControl.selectedItem = $0 }.store(in: &objectBag)
28+
self.$pretty
29+
.sink{[unowned self] in cell.prettySwitch.isOn = $0 }.store(in: &objectBag)
30+
self.$autofix
31+
.sink{[unowned self] in cell.autoFixSwitch.isOn = $0 }.store(in: &objectBag)
32+
33+
self.cell.prettySwitch.isOnPublisher
34+
.sink{[unowned self] in self.pretty = $0; updateFormattedCode() }.store(in: &objectBag)
35+
self.cell.autoFixSwitch.isOnPublisher
36+
.sink{[unowned self] in self.autofix = $0; updateFormattedCode() }.store(in: &objectBag)
37+
self.cell.documentTypeControl.itemPublisher
38+
.sink{[unowned self] in self.documentType = $0; updateFormattedCode() }.store(in: &objectBag)
39+
self.cell.inputSection.stringPublisher
40+
.sink{[unowned self] in self.rawCode = $0; updateFormattedCode() }.store(in: &objectBag)
41+
42+
self.updateFormattedCode()
43+
}
44+
45+
private func updateFormattedCode() {
46+
47+
do {
48+
var options = XMLNode.Options()
49+
50+
if pretty {
51+
options.insert(.nodePrettyPrint)
52+
}
53+
if autofix {
54+
switch documentType {
55+
case .htmlDocument: options.insert(.documentTidyHTML)
56+
case .xmlDocument: options.insert(.documentTidyXML)
57+
}
58+
}
59+
60+
let document = try XMLDocument(xmlString: rawCode, options: options)
61+
62+
self.formattedCode = document.xmlString(options: options)
63+
.replacingOccurrences(of: #"<?xml version="1.0" encoding="UTF-8"?>"#, with: "")
64+
.replacingOccurrences(of: #"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">"#, with: "")
65+
.replacingOccurrences(of: #"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">"#, with: "")
66+
.replacingOccurrences(of: #"<html xmlns="http://www.w3.org/1999/xhtml">"#, with: "<html>")
67+
.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
68+
69+
} catch {
70+
self.formattedCode = "[Invalid Document]"
71+
}
72+
}
73+
}
74+
75+
private enum DocumentType: String, TextItem {
76+
case htmlDocument = "HTML Document"
77+
case xmlDocument = "XML Document"
78+
79+
var title: String { rawValue }
80+
}
81+
82+
final private class XMLFormatterView: Page {
83+
let prettySwitch = NSSwitch()
84+
let autoFixSwitch = NSSwitch()
85+
let documentTypeControl = EnumPopupButton<DocumentType>()
86+
let inputSection = CodeViewSection(title: "Input", options: .defaultInput, language: .xml)
87+
let outputSection = CodeViewSection(title: "Output", options: .defaultOutput, language: .xml)
88+
89+
private lazy var configurationSection = Section(title: "Configuration", items: [
90+
Area(icon: R.Image.convert, title: "Document Type", control: documentTypeControl),
91+
NSStackView() => {
92+
$0.orientation = .horizontal
93+
$0.distribution = .fillEqually
94+
$0.addArrangedSubview(Area(icon: R.Image.format, title: "Auto Fix Document", control: autoFixSwitch))
95+
$0.addArrangedSubview(Area(icon: R.Image.format, title: "Pretty Document", control: prettySwitch))
96+
}
97+
])
98+
99+
private lazy var ioStack = self.addSection2(inputSection, outputSection)
100+
101+
override func layout() {
102+
super.layout()
103+
104+
self.ioStack.snp.remakeConstraints{ make in
105+
make.height.equalTo(max(240, self.frame.height - 160))
106+
}
107+
}
108+
109+
override func onAwake() {
110+
self.title = "XML Formatter"
111+
self.addSection(configurationSection)
112+
}
113+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//
2+
// ImageConverter.swift
3+
// DevToys
4+
//
5+
// Created by yuki on 2022/02/04.
6+
//
7+
8+
import CoreUtil
9+
10+
struct ImageConvertTask {
11+
let image: NSImage
12+
let title: String
13+
let size: CGSize
14+
let isDone: Promise<Void, Error>
15+
}
16+
17+
enum ImageConverter {
18+
private static let destinationDirectory = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask)[0].appendingPathComponent("Converted Images") => {
19+
try? FileManager.default.createDirectory(at: $0, withIntermediateDirectories: true, attributes: nil)
20+
}
21+
22+
static func convert(_ item: ImageItem, format: ImageFormatType, resize: Bool, size: CGSize, scale: ImageScaleMode, padding: Bool) -> ImageConvertTask {
23+
var resizeSize = item.image.size
24+
if resize {
25+
// switch scale {
26+
// case <#pattern#>:
27+
// <#code#>
28+
// default:
29+
// <#code#>
30+
// }
31+
}
32+
33+
// item.image.resized(to: <#T##NSSize#>)
34+
let isDone = Promise<Data, Error>.asyncError{ resolve, reject in
35+
switch format {
36+
case .png:
37+
guard let data = item.image.png else { return reject("Data failed.") }; resolve(data)
38+
case .jpg:
39+
guard let data = item.image.jpeg else { return reject("Data failed.") }; resolve(data)
40+
case .tiff:
41+
guard let data = item.image.tiffRepresentation else { return reject("Data failed.") }; resolve(data)
42+
case .gif:
43+
guard let data = item.image.gif else { return reject("Data failed.") }; resolve(data)
44+
}
45+
}
46+
.tryPeek{ data in
47+
let url = destinationDirectory.appendingPathComponent("\(item.title).\(format.exp)")
48+
try data.write(to: url)
49+
}
50+
.receive(on: .main)
51+
.eraseToVoid()
52+
53+
return ImageConvertTask(image: item.image, title: item.title, size: item.image.size, isDone: isDone)
54+
}
55+
}
56+
57+
extension ImageFormatType {
58+
var exp: String {
59+
switch self {
60+
case .png: return "png"
61+
case .jpg: return "jpg"
62+
case .gif: return "gif"
63+
case .tiff: return "tiff"
64+
}
65+
}
66+
}
67+
68+
69+
extension NSImage {
70+
public var png: Data? { self.data(for: .png) }
71+
public var jpeg: Data? { self.data(for: .jpeg) }
72+
public var gif: Data? { self.data(for: .gif) }
73+
74+
public convenience init(cgImage: CGImage) { self.init(cgImage: cgImage, size: cgImage.size) }
75+
76+
public func data(for fileType: NSBitmapImageRep.FileType, properties: [NSBitmapImageRep.PropertyKey : Any] = [:]) -> Data? {
77+
guard
78+
let tiffRepresentation = self.tiffRepresentation,
79+
let bitmap = NSBitmapImageRep(data: tiffRepresentation),
80+
let rep = bitmap.representation(using: fileType, properties: properties)
81+
else { return nil }
82+
83+
return rep
84+
}
85+
}
86+
87+
extension CGImage {
88+
public var size: CGSize { CGSize(width: self.width, height: self.height) }
89+
}
90+
91+
extension Promise {
92+
public func tryPeek(_ receiveOutput: @escaping (Output) throws -> ()) -> Promise<Output, Error> {
93+
Promise<Output, Error> { resolve, reject in
94+
self.sink({ output in
95+
do { try receiveOutput(output); resolve(output) } catch { reject(error) }
96+
}, reject)
97+
}
98+
}
99+
}
100+
101+
extension NSImage {
102+
var pngData: Data { png! }
103+
104+
func resized(to newSize: NSSize) -> NSImage {
105+
if let bitmapRep = NSBitmapImageRep(
106+
bitmapDataPlanes: nil, pixelsWide: Int(newSize.width), pixelsHigh: Int(newSize.height),
107+
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false,
108+
colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 0
109+
) {
110+
bitmapRep.size = newSize
111+
NSGraphicsContext.saveGraphicsState()
112+
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: bitmapRep)
113+
draw(in: NSRect(x: 0, y: 0, width: newSize.width, height: newSize.height), from: .zero, operation: .copy, fraction: 1.0)
114+
NSGraphicsContext.restoreGraphicsState()
115+
116+
let resizedImage = NSImage(size: newSize)
117+
resizedImage.addRepresentation(bitmapRep)
118+
return resizedImage
119+
}
120+
121+
fatalError()
122+
}
123+
}

0 commit comments

Comments
 (0)