Compare commits
3 Commits
6ba75ab916
...
f3de1b72b6
Author | SHA1 | Date | |
---|---|---|---|
|
f3de1b72b6 | ||
|
3c7681b769 | ||
|
bbb1143600 |
@ -197,6 +197,11 @@
|
|||||||
E2DD04742C276F31003BFF1F /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2DD04732C276F31003BFF1F /* MainView.swift */; };
|
E2DD04742C276F31003BFF1F /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2DD04732C276F31003BFF1F /* MainView.swift */; };
|
||||||
E2DD047A2C276F32003BFF1F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E2DD04792C276F32003BFF1F /* Assets.xcassets */; };
|
E2DD047A2C276F32003BFF1F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E2DD04792C276F32003BFF1F /* Assets.xcassets */; };
|
||||||
E2E06DFB2CA4A65E0019C2AF /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E06DFA2CA4A6570019C2AF /* Content.swift */; };
|
E2E06DFB2CA4A65E0019C2AF /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E06DFA2CA4A6570019C2AF /* Content.swift */; };
|
||||||
|
E2EC1FAB2DC0C99600C41784 /* RouteViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EC1FAA2DC0C98C00C41784 /* RouteViews.swift */; };
|
||||||
|
E2EC1FAD2DC0D2FA00C41784 /* RouteLocalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EC1FAC2DC0D2FA00C41784 /* RouteLocalization.swift */; };
|
||||||
|
E2EC1FB02DC0D7DA00C41784 /* RouteBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EC1FAF2DC0D7D600C41784 /* RouteBlock.swift */; };
|
||||||
|
E2EC1FB22DC0D8BD00C41784 /* RouteViewComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EC1FB12DC0D8BD00C41784 /* RouteViewComponents.swift */; };
|
||||||
|
E2EC1FB42DC0FA8700C41784 /* Insert+Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EC1FB32DC0FA6D00C41784 /* Insert+Route.swift */; };
|
||||||
E2FD1D0D2D2DBBA600B48627 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */; };
|
E2FD1D0D2D2DBBA600B48627 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */; };
|
||||||
E2FD1D192D2DC4F500B48627 /* LoadingContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */; };
|
E2FD1D192D2DC4F500B48627 /* LoadingContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */; };
|
||||||
E2FD1D1B2D2DC63800B48627 /* LinkPreviewDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */; };
|
E2FD1D1B2D2DC63800B48627 /* LinkPreviewDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */; };
|
||||||
@ -470,6 +475,11 @@
|
|||||||
E2DD047D2C276F32003BFF1F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
E2DD047D2C276F32003BFF1F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
E2E06DFA2CA4A6570019C2AF /* Content.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Content.swift; sourceTree = "<group>"; };
|
E2E06DFA2CA4A6570019C2AF /* Content.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Content.swift; sourceTree = "<group>"; };
|
||||||
E2E06DFF2CA4A8EB0019C2AF /* Page+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Page+Mock.swift"; sourceTree = "<group>"; };
|
E2E06DFF2CA4A8EB0019C2AF /* Page+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Page+Mock.swift"; sourceTree = "<group>"; };
|
||||||
|
E2EC1FAA2DC0C98C00C41784 /* RouteViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteViews.swift; sourceTree = "<group>"; };
|
||||||
|
E2EC1FAC2DC0D2FA00C41784 /* RouteLocalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteLocalization.swift; sourceTree = "<group>"; };
|
||||||
|
E2EC1FAF2DC0D7D600C41784 /* RouteBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteBlock.swift; sourceTree = "<group>"; };
|
||||||
|
E2EC1FB12DC0D8BD00C41784 /* RouteViewComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteViewComponents.swift; sourceTree = "<group>"; };
|
||||||
|
E2EC1FB32DC0FA6D00C41784 /* Insert+Route.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Insert+Route.swift"; sourceTree = "<group>"; };
|
||||||
E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreview.swift; sourceTree = "<group>"; };
|
E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreview.swift; sourceTree = "<group>"; };
|
||||||
E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingContext.swift; sourceTree = "<group>"; };
|
E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingContext.swift; sourceTree = "<group>"; };
|
||||||
E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewDetailView.swift; sourceTree = "<group>"; };
|
E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewDetailView.swift; sourceTree = "<group>"; };
|
||||||
@ -712,6 +722,7 @@
|
|||||||
E29D311E2D0320D90051B7F4 /* ContentElements */ = {
|
E29D311E2D0320D90051B7F4 /* ContentElements */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E2EC1FAE2DC0D30100C41784 /* Routes */,
|
||||||
E2BF1BC52D6B16FA003089F1 /* HeadlineLink.swift */,
|
E2BF1BC52D6B16FA003089F1 /* HeadlineLink.swift */,
|
||||||
E2B4821F2D67074C005C309D /* WallpaperSlider.swift */,
|
E2B4821F2D67074C005C309D /* WallpaperSlider.swift */,
|
||||||
E29D31C12D0DBED70051B7F4 /* AudioPlayer */,
|
E29D31C12D0DBED70051B7F4 /* AudioPlayer */,
|
||||||
@ -1063,6 +1074,16 @@
|
|||||||
path = "Preview Content";
|
path = "Preview Content";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E2EC1FAE2DC0D30100C41784 /* Routes */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E2EC1FB12DC0D8BD00C41784 /* RouteViewComponents.swift */,
|
||||||
|
E2EC1FAC2DC0D2FA00C41784 /* RouteLocalization.swift */,
|
||||||
|
E2EC1FAA2DC0C98C00C41784 /* RouteViews.swift */,
|
||||||
|
);
|
||||||
|
path = Routes;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E2FD1D262D2EBBA300B48627 /* Loading */ = {
|
E2FD1D262D2EBBA300B48627 /* Loading */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -1083,6 +1104,7 @@
|
|||||||
E2FD1D382D3BBECA00B48627 /* InsertableView.swift */,
|
E2FD1D382D3BBECA00B48627 /* InsertableView.swift */,
|
||||||
E2FD1D672D483CCA00B48627 /* Insert+Buttons.swift */,
|
E2FD1D672D483CCA00B48627 /* Insert+Buttons.swift */,
|
||||||
E2FD1D362D3BBCB500B48627 /* Insert+Image.swift */,
|
E2FD1D362D3BBCB500B48627 /* Insert+Image.swift */,
|
||||||
|
E2EC1FB32DC0FA6D00C41784 /* Insert+Route.swift */,
|
||||||
E2FD1D552D46CED500B48627 /* Insert+Labels.swift */,
|
E2FD1D552D46CED500B48627 /* Insert+Labels.swift */,
|
||||||
);
|
);
|
||||||
path = Commands;
|
path = Commands;
|
||||||
@ -1125,6 +1147,7 @@
|
|||||||
E2FE0F342D2B27E6002963B7 /* Blocks */ = {
|
E2FE0F342D2B27E6002963B7 /* Blocks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E2EC1FAF2DC0D7D600C41784 /* RouteBlock.swift */,
|
||||||
E2B482212D676BEB005C309D /* PhoneScreensBlock.swift */,
|
E2B482212D676BEB005C309D /* PhoneScreensBlock.swift */,
|
||||||
E2FE0F652D2C3B33002963B7 /* LabelsBlock.swift */,
|
E2FE0F652D2C3B33002963B7 /* LabelsBlock.swift */,
|
||||||
E2FE0F5C2D2BD006002963B7 /* Types */,
|
E2FE0F5C2D2BD006002963B7 /* Types */,
|
||||||
@ -1306,6 +1329,7 @@
|
|||||||
E29D313B2D04464A0051B7F4 /* LocalizedTagDetailView.swift in Sources */,
|
E29D313B2D04464A0051B7F4 /* LocalizedTagDetailView.swift in Sources */,
|
||||||
E2FE0F552D2BCFC4002963B7 /* ContentBlock.swift in Sources */,
|
E2FE0F552D2BCFC4002963B7 /* ContentBlock.swift in Sources */,
|
||||||
E2A37D212CEA94EC0000979F /* Sequence+Sorted.swift in Sources */,
|
E2A37D212CEA94EC0000979F /* Sequence+Sorted.swift in Sources */,
|
||||||
|
E2EC1FB02DC0D7DA00C41784 /* RouteBlock.swift in Sources */,
|
||||||
E29D31BA2D0DB5080051B7F4 /* LabelsCommand.swift in Sources */,
|
E29D31BA2D0DB5080051B7F4 /* LabelsCommand.swift in Sources */,
|
||||||
E2FE0EFC2D266D22002963B7 /* NavigationSettings.swift in Sources */,
|
E2FE0EFC2D266D22002963B7 /* NavigationSettings.swift in Sources */,
|
||||||
E29D31692D0702700051B7F4 /* TextFileContentView.swift in Sources */,
|
E29D31692D0702700051B7F4 /* TextFileContentView.swift in Sources */,
|
||||||
@ -1395,6 +1419,7 @@
|
|||||||
E20BCC9F2D53851400B8DBEB /* SelectableListItem.swift in Sources */,
|
E20BCC9F2D53851400B8DBEB /* SelectableListItem.swift in Sources */,
|
||||||
E20BCCAB2D53B86900B8DBEB /* GenerationResultsIssueView.swift in Sources */,
|
E20BCCAB2D53B86900B8DBEB /* GenerationResultsIssueView.swift in Sources */,
|
||||||
E2B482092D5E7F4F005C309D /* WebsitePreviewSheet.swift in Sources */,
|
E2B482092D5E7F4F005C309D /* WebsitePreviewSheet.swift in Sources */,
|
||||||
|
E2EC1FAD2DC0D2FA00C41784 /* RouteLocalization.swift in Sources */,
|
||||||
E22990262D0F582B009F8D77 /* FilePropertyView.swift in Sources */,
|
E22990262D0F582B009F8D77 /* FilePropertyView.swift in Sources */,
|
||||||
E2FD1D462D46428100B48627 /* PageIconView.swift in Sources */,
|
E2FD1D462D46428100B48627 /* PageIconView.swift in Sources */,
|
||||||
E2A37D252CEBD7A10000979F /* PageListView.swift in Sources */,
|
E2A37D252CEBD7A10000979F /* PageListView.swift in Sources */,
|
||||||
@ -1425,6 +1450,7 @@
|
|||||||
E25DA5752D018B6100AEF16D /* FileDetailView.swift in Sources */,
|
E25DA5752D018B6100AEF16D /* FileDetailView.swift in Sources */,
|
||||||
E25DA50B2CFD988100AEF16D /* PageTagAssignmentView.swift in Sources */,
|
E25DA50B2CFD988100AEF16D /* PageTagAssignmentView.swift in Sources */,
|
||||||
E2FD1D682D483CCF00B48627 /* Insert+Buttons.swift in Sources */,
|
E2FD1D682D483CCF00B48627 /* Insert+Buttons.swift in Sources */,
|
||||||
|
E2EC1FB22DC0D8BD00C41784 /* RouteViewComponents.swift in Sources */,
|
||||||
E29D31A12D0C75CA0051B7F4 /* Content+Validation.swift in Sources */,
|
E29D31A12D0C75CA0051B7F4 /* Content+Validation.swift in Sources */,
|
||||||
E29D316D2D07A5050051B7F4 /* PageGenerationResults.swift in Sources */,
|
E29D316D2D07A5050051B7F4 /* PageGenerationResults.swift in Sources */,
|
||||||
E229903E2D0F8F02009F8D77 /* StringPropertyView.swift in Sources */,
|
E229903E2D0F8F02009F8D77 /* StringPropertyView.swift in Sources */,
|
||||||
@ -1519,6 +1545,7 @@
|
|||||||
E25DA5712D01015400AEF16D /* GenerationContentView.swift in Sources */,
|
E25DA5712D01015400AEF16D /* GenerationContentView.swift in Sources */,
|
||||||
E29D316F2D0822770051B7F4 /* SettingsListView.swift in Sources */,
|
E29D316F2D0822770051B7F4 /* SettingsListView.swift in Sources */,
|
||||||
E2A21C0E2CB189DC0060935B /* Color+RGB.swift in Sources */,
|
E2A21C0E2CB189DC0060935B /* Color+RGB.swift in Sources */,
|
||||||
|
E2EC1FAB2DC0C99600C41784 /* RouteViews.swift in Sources */,
|
||||||
E25DA5852D01C92700AEF16D /* CommandType.swift in Sources */,
|
E25DA5852D01C92700AEF16D /* CommandType.swift in Sources */,
|
||||||
E229903A2D0F7E48009F8D77 /* GenericPropertyView.swift in Sources */,
|
E229903A2D0F7E48009F8D77 /* GenericPropertyView.swift in Sources */,
|
||||||
E29D31AA2D0CEE3F0051B7F4 /* AudioPlayer.swift in Sources */,
|
E29D31AA2D0CEE3F0051B7F4 /* AudioPlayer.swift in Sources */,
|
||||||
@ -1533,6 +1560,7 @@
|
|||||||
E2FD1D602D47EEEF00B48627 /* LocalizedPostContentView.swift in Sources */,
|
E2FD1D602D47EEEF00B48627 /* LocalizedPostContentView.swift in Sources */,
|
||||||
E229903C2D0F8A7B009F8D77 /* OptionalTextFieldPropertyView.swift in Sources */,
|
E229903C2D0F8A7B009F8D77 /* OptionalTextFieldPropertyView.swift in Sources */,
|
||||||
E2FD1D582D477A9400B48627 /* InsertableCommand.swift in Sources */,
|
E2FD1D582D477A9400B48627 /* InsertableCommand.swift in Sources */,
|
||||||
|
E2EC1FB42DC0FA8700C41784 /* Insert+Route.swift in Sources */,
|
||||||
E29D31222D0363FD0051B7F4 /* ContentButtons.swift in Sources */,
|
E29D31222D0363FD0051B7F4 /* ContentButtons.swift in Sources */,
|
||||||
E2FE0F222D2A84A0002963B7 /* VideoCommand.swift in Sources */,
|
E2FE0F222D2A84A0002963B7 /* VideoCommand.swift in Sources */,
|
||||||
E2FE0F192D2723E3002963B7 /* ImageSet.swift in Sources */,
|
E2FE0F192D2723E3002963B7 /* ImageSet.swift in Sources */,
|
||||||
|
@ -15,6 +15,8 @@ enum ContentBlock: String, CaseIterable {
|
|||||||
|
|
||||||
case screens
|
case screens
|
||||||
|
|
||||||
|
case route
|
||||||
|
|
||||||
var processor: BlockProcessor.Type {
|
var processor: BlockProcessor.Type {
|
||||||
switch self {
|
switch self {
|
||||||
case .audio: return AudioBlock.self
|
case .audio: return AudioBlock.self
|
||||||
@ -24,6 +26,7 @@ enum ContentBlock: String, CaseIterable {
|
|||||||
case .buttons: return ButtonsBlock.self
|
case .buttons: return ButtonsBlock.self
|
||||||
case .labels: return LabelsBlock.self
|
case .labels: return LabelsBlock.self
|
||||||
case .screens: return PhoneScreensBlock.self
|
case .screens: return PhoneScreensBlock.self
|
||||||
|
case .route: return RouteBlock.self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
82
CHDataManagement/Generator/Blocks/RouteBlock.swift
Normal file
82
CHDataManagement/Generator/Blocks/RouteBlock.swift
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
|
||||||
|
struct RouteBlock: KeyedBlockProcessor {
|
||||||
|
|
||||||
|
enum Key: String {
|
||||||
|
case chartTitle
|
||||||
|
case components
|
||||||
|
case mapTitle
|
||||||
|
case image
|
||||||
|
case caption
|
||||||
|
case file
|
||||||
|
}
|
||||||
|
|
||||||
|
static let blockId: ContentBlock = .route
|
||||||
|
|
||||||
|
let content: Content
|
||||||
|
|
||||||
|
let results: PageGenerationResults
|
||||||
|
|
||||||
|
let language: ContentLanguage
|
||||||
|
|
||||||
|
init(content: Content, results: PageGenerationResults, language: ContentLanguage) {
|
||||||
|
self.content = content
|
||||||
|
self.results = results
|
||||||
|
self.language = language
|
||||||
|
}
|
||||||
|
|
||||||
|
private var thumbnailWidth: Int {
|
||||||
|
content.settings.pages.contentWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
private var largeImageWidth: Int {
|
||||||
|
content.settings.pages.largeImageWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
func process(_ arguments: [Key : String], markdown: Substring) -> String {
|
||||||
|
let rawComponents = arguments[.components] ?? "all"
|
||||||
|
guard let imageId = arguments[.image],
|
||||||
|
let fileId = arguments[.file],
|
||||||
|
let components = RouteViewComponents(rawValue: rawComponents) else {
|
||||||
|
invalid(markdown)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let image = content.image(imageId) else {
|
||||||
|
results.missing(file: imageId, source: "Route block")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
guard let file = content.file(fileId) else {
|
||||||
|
results.missing(file: imageId, source: "Route block")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
results.used(file: image)
|
||||||
|
results.require(file: file)
|
||||||
|
|
||||||
|
let thumbnail = image.imageSet(width: thumbnailWidth, height: thumbnailWidth, language: language)
|
||||||
|
results.require(imageSet: thumbnail)
|
||||||
|
|
||||||
|
let largeImage = image.imageSet(width: largeImageWidth, height: largeImageWidth, language: language)
|
||||||
|
results.require(imageSet: largeImage)
|
||||||
|
|
||||||
|
results.require(header: .routeJs)
|
||||||
|
|
||||||
|
let id = imageId.replacingOccurrences(of: ".", with: "-")
|
||||||
|
|
||||||
|
let views = RouteViews(
|
||||||
|
localization: language == .english ? .english : .german,
|
||||||
|
chartTitle: arguments[.chartTitle],
|
||||||
|
chartId: "chart-" + id,
|
||||||
|
components: components,
|
||||||
|
mapTitle: arguments[.mapTitle],
|
||||||
|
mapId: "map-" + id,
|
||||||
|
filePath: file.absoluteUrl,
|
||||||
|
imageId: "image-" + id,
|
||||||
|
thumbnail: thumbnail,
|
||||||
|
largeImage: largeImage,
|
||||||
|
caption: arguments[.caption])
|
||||||
|
|
||||||
|
results.require(footer: views.script)
|
||||||
|
|
||||||
|
return views.content
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,9 @@ enum KnownHeaderElement {
|
|||||||
|
|
||||||
case swiperJs
|
case swiperJs
|
||||||
|
|
||||||
|
/// The Javascript to compute and animate route statistics
|
||||||
|
case routeJs
|
||||||
|
|
||||||
case style(String)
|
case style(String)
|
||||||
|
|
||||||
func header(content: Content) -> HeaderElement? {
|
func header(content: Content) -> HeaderElement? {
|
||||||
@ -55,6 +58,10 @@ enum KnownHeaderElement {
|
|||||||
if let swiperJs = content.settings.posts.swiperJsFile {
|
if let swiperJs = content.settings.posts.swiperJsFile {
|
||||||
return .js(file: swiperJs, defer: true)
|
return .js(file: swiperJs, defer: true)
|
||||||
}
|
}
|
||||||
|
case .routeJs:
|
||||||
|
if let routeJs = content.settings.pages.routeJsFile {
|
||||||
|
return .js(file: routeJs, defer: false)
|
||||||
|
}
|
||||||
case .style(let code):
|
case .style(let code):
|
||||||
return .style(code)
|
return .style(code)
|
||||||
}
|
}
|
||||||
@ -96,6 +103,8 @@ extension KnownHeaderElement: CustomStringConvertible {
|
|||||||
return "swiper-css"
|
return "swiper-css"
|
||||||
case .swiperJs:
|
case .swiperJs:
|
||||||
return "swiper-js"
|
return "swiper-js"
|
||||||
|
case .routeJs:
|
||||||
|
return "route-js"
|
||||||
case .style(let style):
|
case .style(let style):
|
||||||
return "style: " + style
|
return "style: " + style
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,9 @@ final class GenerationResults: ObservableObject {
|
|||||||
@Published
|
@Published
|
||||||
var requiredFiles: Set<FileResource> = []
|
var requiredFiles: Set<FileResource> = []
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var externalFiles: Set<FileResource> = []
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var imagesToGenerate: Set<ImageVersion> = []
|
var imagesToGenerate: Set<ImageVersion> = []
|
||||||
|
|
||||||
@ -113,6 +116,7 @@ final class GenerationResults: ObservableObject {
|
|||||||
self.missingPages = []
|
self.missingPages = []
|
||||||
self.externalLinks = []
|
self.externalLinks = []
|
||||||
self.requiredFiles = []
|
self.requiredFiles = []
|
||||||
|
self.externalFiles = []
|
||||||
self.imagesToGenerate = []
|
self.imagesToGenerate = []
|
||||||
self.invalidCommands = []
|
self.invalidCommands = []
|
||||||
self.invalidBlocks = []
|
self.invalidBlocks = []
|
||||||
@ -143,6 +147,8 @@ final class GenerationResults: ObservableObject {
|
|||||||
update { self.externalLinks = externalLinks }
|
update { self.externalLinks = externalLinks }
|
||||||
let requiredFiles = cache.values.map { $0.requiredFiles }.union()
|
let requiredFiles = cache.values.map { $0.requiredFiles }.union()
|
||||||
update { self.requiredFiles = requiredFiles }
|
update { self.requiredFiles = requiredFiles }
|
||||||
|
let externalFiles = cache.values.map { $0.requiredFiles.filter { $0.isExternallyStored } }.union()
|
||||||
|
update { self.externalFiles = externalFiles }
|
||||||
let imagesToGenerate = cache.values.map { $0.imagesToGenerate }.union()
|
let imagesToGenerate = cache.values.map { $0.imagesToGenerate }.union()
|
||||||
update { self.imagesToGenerate = imagesToGenerate }
|
update { self.imagesToGenerate = imagesToGenerate }
|
||||||
let invalidCommands = cache.values.map { $0.invalidCommands.map { $0.markdown }}.union()
|
let invalidCommands = cache.values.map { $0.invalidCommands.map { $0.markdown }}.union()
|
||||||
@ -195,11 +201,19 @@ final class GenerationResults: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func require(file: FileResource) {
|
func require(file: FileResource) {
|
||||||
update { self.requiredFiles.insert(file) }
|
update {
|
||||||
|
self.requiredFiles.insert(file)
|
||||||
|
if file.isExternallyStored {
|
||||||
|
self.externalFiles.insert(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func require<S>(files: S) where S: Sequence, S.Element == FileResource {
|
func require<S>(files: S) where S: Sequence, S.Element == FileResource {
|
||||||
update { self.requiredFiles.formUnion(files) }
|
update {
|
||||||
|
self.requiredFiles.formUnion(files)
|
||||||
|
self.externalFiles.formUnion(files.filter { $0.isExternallyStored })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generate(_ image: ImageVersion) {
|
func generate(_ image: ImageVersion) {
|
||||||
|
@ -108,7 +108,11 @@ final class FileResource: Item, LocalizedItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func save(textContent: String) -> Bool {
|
func save(textContent: String) -> Bool {
|
||||||
content.storage.save(fileContent: textContent, for: id)
|
guard content.storage.save(fileContent: textContent, for: id) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
modifiedDate = .now
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func dataContent() -> Foundation.Data? {
|
func dataContent() -> Foundation.Data? {
|
||||||
|
@ -90,6 +90,8 @@ enum FileType: String {
|
|||||||
|
|
||||||
case txt
|
case txt
|
||||||
|
|
||||||
|
case gpx
|
||||||
|
|
||||||
// MARK: Model
|
// MARK: Model
|
||||||
|
|
||||||
case stl
|
case stl
|
||||||
@ -164,7 +166,7 @@ enum FileType: String {
|
|||||||
return .video
|
return .video
|
||||||
case .mp3, .aac, .m4b, .m4a:
|
case .mp3, .aac, .m4b, .m4a:
|
||||||
return .audio
|
return .audio
|
||||||
case .json, .conf, .yaml, .txt:
|
case .json, .conf, .yaml, .txt, .gpx:
|
||||||
return .text
|
return .text
|
||||||
case .html, .cpp, .swift, .sh, .js, .css:
|
case .html, .cpp, .swift, .sh, .js, .css:
|
||||||
return .code
|
return .code
|
||||||
|
@ -29,6 +29,9 @@ final class PageSettings: ObservableObject {
|
|||||||
@Published
|
@Published
|
||||||
var manifestFile: FileResource?
|
var manifestFile: FileResource?
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var routeJsFile: FileResource?
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var german: LocalizedPageSettings
|
var german: LocalizedPageSettings
|
||||||
|
|
||||||
@ -44,6 +47,7 @@ final class PageSettings: ObservableObject {
|
|||||||
imageCompareJsFile: FileResource? = nil,
|
imageCompareJsFile: FileResource? = nil,
|
||||||
imageCompareCssFile: FileResource? = nil,
|
imageCompareCssFile: FileResource? = nil,
|
||||||
manifestFile: FileResource? = nil,
|
manifestFile: FileResource? = nil,
|
||||||
|
routeJsFile: FileResource? = nil,
|
||||||
german: LocalizedPageSettings,
|
german: LocalizedPageSettings,
|
||||||
english: LocalizedPageSettings) {
|
english: LocalizedPageSettings) {
|
||||||
self.contentWidth = contentWidth
|
self.contentWidth = contentWidth
|
||||||
@ -55,6 +59,7 @@ final class PageSettings: ObservableObject {
|
|||||||
self.imageCompareJsFile = imageCompareJsFile
|
self.imageCompareJsFile = imageCompareJsFile
|
||||||
self.imageCompareCssFile = imageCompareCssFile
|
self.imageCompareCssFile = imageCompareCssFile
|
||||||
self.manifestFile = manifestFile
|
self.manifestFile = manifestFile
|
||||||
|
self.routeJsFile = routeJsFile
|
||||||
self.german = german
|
self.german = german
|
||||||
self.english = english
|
self.english = english
|
||||||
}
|
}
|
||||||
@ -78,6 +83,9 @@ final class PageSettings: ObservableObject {
|
|||||||
if manifestFile == file {
|
if manifestFile == file {
|
||||||
manifestFile = nil
|
manifestFile = nil
|
||||||
}
|
}
|
||||||
|
if routeJsFile == file {
|
||||||
|
routeJsFile = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +104,7 @@ extension PageSettings {
|
|||||||
imageCompareJsFile: data.imageCompareJsFile.map(context.file),
|
imageCompareJsFile: data.imageCompareJsFile.map(context.file),
|
||||||
imageCompareCssFile: data.imageCompareCssFile.map(context.file),
|
imageCompareCssFile: data.imageCompareCssFile.map(context.file),
|
||||||
manifestFile: data.manifestFile.map(context.file),
|
manifestFile: data.manifestFile.map(context.file),
|
||||||
|
routeJsFile: data.routeJsFile.map(context.file),
|
||||||
german: .init(data: data.german),
|
german: .init(data: data.german),
|
||||||
english: .init(data: data.english))
|
english: .init(data: data.english))
|
||||||
}
|
}
|
||||||
@ -110,6 +119,7 @@ extension PageSettings {
|
|||||||
imageCompareJsFile: imageCompareJsFile?.id,
|
imageCompareJsFile: imageCompareJsFile?.id,
|
||||||
imageCompareCssFile: imageCompareCssFile?.id,
|
imageCompareCssFile: imageCompareCssFile?.id,
|
||||||
manifestFile: manifestFile?.id,
|
manifestFile: manifestFile?.id,
|
||||||
|
routeJsFile: routeJsFile?.id,
|
||||||
german: german.data,
|
german: german.data,
|
||||||
english: english.data)
|
english: english.data)
|
||||||
}
|
}
|
||||||
@ -124,6 +134,7 @@ extension PageSettings {
|
|||||||
let imageCompareJsFile: String?
|
let imageCompareJsFile: String?
|
||||||
let imageCompareCssFile: String?
|
let imageCompareCssFile: String?
|
||||||
let manifestFile: String?
|
let manifestFile: String?
|
||||||
|
let routeJsFile: String?
|
||||||
let german: LocalizedPageSettings.Data
|
let german: LocalizedPageSettings.Data
|
||||||
let english: LocalizedPageSettings.Data
|
let english: LocalizedPageSettings.Data
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,41 @@ struct PageImage: HtmlProducer {
|
|||||||
/// The optional caption text below the fullscreen image
|
/// The optional caption text below the fullscreen image
|
||||||
let caption: String?
|
let caption: String?
|
||||||
|
|
||||||
|
/// The id of the image container
|
||||||
|
let id: String?
|
||||||
|
|
||||||
|
/// Additional class names for the image container
|
||||||
|
let className: String?
|
||||||
|
|
||||||
|
/// Additional content for the image container
|
||||||
|
let imageContent: String?
|
||||||
|
|
||||||
|
init(imageId: String, thumbnail: ImageSet, largeImage: ImageSet, caption: String?, id: String? = nil, className: String? = nil, imageContent: String? = nil) {
|
||||||
|
self.imageId = imageId
|
||||||
|
self.thumbnail = thumbnail
|
||||||
|
self.largeImage = largeImage
|
||||||
|
self.caption = caption
|
||||||
|
self.id = id
|
||||||
|
self.className = className
|
||||||
|
self.imageContent = imageContent
|
||||||
|
}
|
||||||
|
|
||||||
|
var idString: String {
|
||||||
|
guard let id else { return "" }
|
||||||
|
return " id='\(id)'"
|
||||||
|
}
|
||||||
|
|
||||||
|
var classString: String {
|
||||||
|
guard let className else { return "" }
|
||||||
|
return " \(className)"
|
||||||
|
}
|
||||||
|
|
||||||
func populate(_ result: inout String) {
|
func populate(_ result: inout String) {
|
||||||
result += "<div class='content-image' onclick=\"document.getElementById('\(imageId)').classList.add('active')\">"
|
result += "<div\(idString) class='content-image\(classString)' onclick=\"document.getElementById('\(imageId)').classList.add('active')\">"
|
||||||
result += thumbnail.content
|
result += thumbnail.content
|
||||||
|
if let imageContent {
|
||||||
|
result += imageContent
|
||||||
|
}
|
||||||
result += "</div>"
|
result += "</div>"
|
||||||
result += "<div id='\(imageId)' class='fullscreen-image' onclick=\"document.getElementById('\(imageId)').classList.remove('active')\">"
|
result += "<div id='\(imageId)' class='fullscreen-image' onclick=\"document.getElementById('\(imageId)').classList.remove('active')\">"
|
||||||
result += largeImage.content
|
result += largeImage.content
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
|
||||||
|
struct RouteLocalization {
|
||||||
|
|
||||||
|
let elevation: String
|
||||||
|
|
||||||
|
let speed: String
|
||||||
|
|
||||||
|
let pace: String
|
||||||
|
|
||||||
|
let heartRate: String
|
||||||
|
|
||||||
|
let fallback: String
|
||||||
|
|
||||||
|
let hourUnit: String
|
||||||
|
|
||||||
|
let duration: String
|
||||||
|
|
||||||
|
let time: String
|
||||||
|
|
||||||
|
let distance: String
|
||||||
|
|
||||||
|
let loadFail: String
|
||||||
|
}
|
||||||
|
|
||||||
|
extension RouteLocalization {
|
||||||
|
|
||||||
|
static let german: RouteLocalization = .init(
|
||||||
|
elevation: "Höhe",
|
||||||
|
speed: "Geschw.",
|
||||||
|
pace: "Pace",
|
||||||
|
heartRate: "Herzfrequenz",
|
||||||
|
fallback: "Zur Anzeige der Statistiken wird JavaScript und Unterstützung für HTML5 Canvas benötigt.",
|
||||||
|
hourUnit: "Std",
|
||||||
|
duration: "Dauer",
|
||||||
|
time: "Zeit",
|
||||||
|
distance: "Distanz",
|
||||||
|
loadFail: "Die Statistiken konnten nicht geladen werden")
|
||||||
|
|
||||||
|
static let english: RouteLocalization = .init(
|
||||||
|
elevation: "Elevation",
|
||||||
|
speed: "Speed",
|
||||||
|
pace: "Pace",
|
||||||
|
heartRate: "Heart Rate",
|
||||||
|
fallback: "Javascript and HTML5 Canvas Support are required to display statistics",
|
||||||
|
hourUnit: "h",
|
||||||
|
duration: "Duration",
|
||||||
|
time: "Time",
|
||||||
|
distance: "Distance",
|
||||||
|
loadFail: "The statistics could not be loaded")
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
enum RouteViewComponents: String {
|
||||||
|
case onlyElevation = "elevation"
|
||||||
|
case all = "all"
|
||||||
|
case withoutHeartRate = "no-hr"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
|||||||
|
|
||||||
|
struct RouteViews: HtmlProducer {
|
||||||
|
|
||||||
|
let localization: RouteLocalization
|
||||||
|
|
||||||
|
/// The HTML id attribute used to enable fullscreen images
|
||||||
|
let map: PageImage
|
||||||
|
|
||||||
|
let components: RouteViewComponents
|
||||||
|
|
||||||
|
let mapId: String
|
||||||
|
|
||||||
|
let mapTitle: String?
|
||||||
|
|
||||||
|
let filePath: String
|
||||||
|
|
||||||
|
let chartId: String
|
||||||
|
|
||||||
|
let chartTitle: String?
|
||||||
|
|
||||||
|
init(localization: RouteLocalization,
|
||||||
|
chartTitle: String?,
|
||||||
|
chartId: String,
|
||||||
|
components: RouteViewComponents,
|
||||||
|
mapTitle: String?,
|
||||||
|
mapId: String,
|
||||||
|
filePath: String,
|
||||||
|
imageId: String,
|
||||||
|
thumbnail: ImageSet,
|
||||||
|
largeImage: ImageSet,
|
||||||
|
caption: String?
|
||||||
|
) {
|
||||||
|
self.localization = localization
|
||||||
|
self.components = components
|
||||||
|
self.mapId = mapId
|
||||||
|
self.filePath = filePath
|
||||||
|
self.map = PageImage(
|
||||||
|
imageId: imageId,
|
||||||
|
thumbnail: thumbnail,
|
||||||
|
largeImage: largeImage,
|
||||||
|
caption: caption,
|
||||||
|
id: mapId,
|
||||||
|
className: "map-container",
|
||||||
|
imageContent: "<div class='marker'></div>")
|
||||||
|
self.chartTitle = chartTitle
|
||||||
|
self.chartId = chartId
|
||||||
|
self.mapTitle = mapTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
var pickerHiddenText: String {
|
||||||
|
guard components == .onlyElevation else { return "" }
|
||||||
|
return " style='display:none'"
|
||||||
|
}
|
||||||
|
|
||||||
|
func populate(_ result: inout String) {
|
||||||
|
if let mapTitle {
|
||||||
|
result += "<h2>\(mapTitle)</h2>"
|
||||||
|
}
|
||||||
|
map.populate(&result)
|
||||||
|
|
||||||
|
if let chartTitle {
|
||||||
|
result += "<h2>\(chartTitle)</h2>"
|
||||||
|
}
|
||||||
|
result += "<div id='\(chartId)' class='charts'>"
|
||||||
|
result += "<div class='picker y-picker'\(pickerHiddenText)>"
|
||||||
|
result += "<button data-type='elevation' unit='m' class='active'>\(localization.elevation)</button>"
|
||||||
|
result += "<button data-type='speed' unit='km/h'>\(localization.speed)</button>"
|
||||||
|
result += "<button data-type='pace' unit='min/km'>\(localization.pace)</button>"
|
||||||
|
if components == .all {
|
||||||
|
result += "<button data-type='hr' unit='bpm'>\(localization.heartRate)</button>"
|
||||||
|
}
|
||||||
|
result += "</div>"
|
||||||
|
result += "<div class='graph'>"
|
||||||
|
result += "<canvas>"
|
||||||
|
result += "<div class='fallback'>\(localization.fallback)</div>"
|
||||||
|
result += "</canvas>"
|
||||||
|
result += "<div class='line'></div>"
|
||||||
|
result += "<div class='tooltip'></div>"
|
||||||
|
result += "<div class='load-error'>\(localization.loadFail)</div>"
|
||||||
|
result += "</div>"
|
||||||
|
result += "<div class='picker x-picker'\(pickerHiddenText)>"
|
||||||
|
result += "<button data-type='distance' unit='km' class='active'>\(localization.distance)</button>"
|
||||||
|
result += "<button data-type='duration' unit='\(localization.hourUnit)'>\(localization.duration)</button>"
|
||||||
|
result += "<button data-type='time' unit=''>\(localization.time)</button>"
|
||||||
|
result += "</div>"
|
||||||
|
result += "</div>"
|
||||||
|
}
|
||||||
|
|
||||||
|
var script: String {
|
||||||
|
"""
|
||||||
|
<script>
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initializeGraphs(document, '\(mapId)', '\(chartId)', '\(filePath)');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
@ -41,7 +41,7 @@ struct FileContentView: View {
|
|||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
case .text, .code:
|
case .text, .code:
|
||||||
TextFileContentView(file: file)
|
TextFileContentView(file: file)
|
||||||
.id(file.id)
|
.id(file.id + file.modifiedDate.description)
|
||||||
case .video:
|
case .video:
|
||||||
VStack {
|
VStack {
|
||||||
if let image = file.imageToDisplay {
|
if let image = file.imageToDisplay {
|
||||||
|
@ -11,6 +11,9 @@ struct TextFileContentView: View {
|
|||||||
@State
|
@State
|
||||||
private var loadedFile: String?
|
private var loadedFile: String?
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var loadedFileDate: Date = .now
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
if fileContent != "" {
|
if fileContent != "" {
|
||||||
@ -47,6 +50,7 @@ struct TextFileContentView: View {
|
|||||||
private func reload() {
|
private func reload() {
|
||||||
fileContent = file.textContent()
|
fileContent = file.textContent()
|
||||||
loadedFile = file.id
|
loadedFile = file.id
|
||||||
|
loadedFileDate = file.modifiedDate
|
||||||
print("Loaded content of file \(file.id)")
|
print("Loaded content of file \(file.id)")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +61,12 @@ struct TextFileContentView: View {
|
|||||||
}
|
}
|
||||||
guard loadedFile == file.id else {
|
guard loadedFile == file.id else {
|
||||||
print("[ERROR] Text File View: Not saving since file changed")
|
print("[ERROR] Text File View: Not saving since file changed")
|
||||||
|
reload()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard loadedFileDate == file.modifiedDate else {
|
||||||
|
print("Text File View: Not saving changed file \(file.id)")
|
||||||
|
reload()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard fileContent != "" else {
|
guard fileContent != "" else {
|
||||||
@ -67,6 +77,7 @@ struct TextFileContentView: View {
|
|||||||
print("[ERROR] Text File View: Failed to save file \(file.id)")
|
print("[ERROR] Text File View: Failed to save file \(file.id)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
loadedFileDate = file.modifiedDate
|
||||||
print("Text File View: Saved file \(file.id)")
|
print("Text File View: Saved file \(file.id)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,10 @@ struct GenerationContentView: View {
|
|||||||
}
|
}
|
||||||
Text(content.generationStatus)
|
Text(content.generationStatus)
|
||||||
.padding(.vertical, 5)
|
.padding(.vertical, 5)
|
||||||
|
GenerationStringIssuesView(
|
||||||
|
text: "output files",
|
||||||
|
statusWhenNonEmpty: .nominal,
|
||||||
|
items: $content.results.outputFiles)
|
||||||
GenerationResultsIssueView(
|
GenerationResultsIssueView(
|
||||||
text: "\(content.results.imagesToGenerate.count) images",
|
text: "\(content.results.imagesToGenerate.count) images",
|
||||||
status: .nominal,
|
status: .nominal,
|
||||||
@ -58,10 +62,18 @@ struct GenerationContentView: View {
|
|||||||
text: "required files",
|
text: "required files",
|
||||||
statusWhenNonEmpty: .nominal,
|
statusWhenNonEmpty: .nominal,
|
||||||
items: $content.results.requiredFiles) { $0.id }
|
items: $content.results.requiredFiles) { $0.id }
|
||||||
|
GenerationStringIssuesView(
|
||||||
|
text: "external files",
|
||||||
|
statusWhenNonEmpty: .nominal,
|
||||||
|
items: $content.results.externalFiles) { $0.id }
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "empty pages",
|
text: "empty pages",
|
||||||
statusWhenNonEmpty: .warning,
|
statusWhenNonEmpty: .warning,
|
||||||
items: $content.results.emptyPages) { "\($0.pageId) (\($0.language))" }
|
items: $content.results.emptyPages) { "\($0.pageId) (\($0.language))" }
|
||||||
|
GenerationStringIssuesView(
|
||||||
|
text: "additional output files",
|
||||||
|
statusWhenNonEmpty: .warning,
|
||||||
|
items: $content.results.unusedFilesInOutput)
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "inaccessible files",
|
text: "inaccessible files",
|
||||||
items: $content.results.inaccessibleFiles) { $0.id }
|
items: $content.results.inaccessibleFiles) { $0.id }
|
||||||
@ -89,14 +101,6 @@ struct GenerationContentView: View {
|
|||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "invalid blocks",
|
text: "invalid blocks",
|
||||||
items: $content.results.invalidBlocks)
|
items: $content.results.invalidBlocks)
|
||||||
GenerationStringIssuesView(
|
|
||||||
text: "output files",
|
|
||||||
statusWhenNonEmpty: .nominal,
|
|
||||||
items: $content.results.outputFiles)
|
|
||||||
GenerationStringIssuesView(
|
|
||||||
text: "additional output files",
|
|
||||||
statusWhenNonEmpty: .warning,
|
|
||||||
items: $content.results.unusedFilesInOutput)
|
|
||||||
GenerationStringIssuesView(
|
GenerationStringIssuesView(
|
||||||
text: "warnings",
|
text: "warnings",
|
||||||
statusWhenNonEmpty: .warning,
|
statusWhenNonEmpty: .warning,
|
||||||
|
111
CHDataManagement/Views/Pages/Commands/Insert+Route.swift
Normal file
111
CHDataManagement/Views/Pages/Commands/Insert+Route.swift
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import SFSafeSymbols
|
||||||
|
|
||||||
|
struct InsertableRoute: View, InsertableCommandView {
|
||||||
|
|
||||||
|
final class Model: ObservableObject, InsertableCommandModel {
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var caption: String?
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var chartTitle: String?
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var components: RouteViewComponents = .all
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var mapTitle: String?
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var selectedImage: FileResource?
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var dataFile: FileResource?
|
||||||
|
|
||||||
|
var isReady: Bool {
|
||||||
|
selectedImage != nil && dataFile != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var command: String? {
|
||||||
|
guard let selectedImage,
|
||||||
|
let dataFile else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var result = ["```route"]
|
||||||
|
result.append("\(RouteBlock.Key.image.rawValue): \(selectedImage.id)")
|
||||||
|
result.append("\(RouteBlock.Key.file.rawValue): \(dataFile.id)")
|
||||||
|
result.append("\(RouteBlock.Key.components.rawValue): \(components.rawValue)")
|
||||||
|
if let caption {
|
||||||
|
result.append("\(RouteBlock.Key.caption.rawValue): \(caption)")
|
||||||
|
}
|
||||||
|
if let chartTitle {
|
||||||
|
result.append("\(RouteBlock.Key.chartTitle.rawValue): \(chartTitle)")
|
||||||
|
}
|
||||||
|
if let mapTitle {
|
||||||
|
result.append("\(RouteBlock.Key.mapTitle.rawValue): \(mapTitle)")
|
||||||
|
}
|
||||||
|
result.append("\n```")
|
||||||
|
return result.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static let title = "Route"
|
||||||
|
|
||||||
|
static let sheetTitle = "Insert route map and statistics"
|
||||||
|
|
||||||
|
static let icon: SFSymbol = .map
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
private var model: Model
|
||||||
|
|
||||||
|
init(model: Model) {
|
||||||
|
self.model = model
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
FilePropertyView(
|
||||||
|
title: "Map Image",
|
||||||
|
footer: "Select the image to insert as the map",
|
||||||
|
selectedFile: $model.selectedImage,
|
||||||
|
allowedType: .image)
|
||||||
|
FilePropertyView(
|
||||||
|
title: "Data File",
|
||||||
|
footer: "Select the file containing the statistics",
|
||||||
|
selectedFile: $model.dataFile,
|
||||||
|
allowedType: .text)
|
||||||
|
OptionalStringPropertyView(
|
||||||
|
title: "Map Title",
|
||||||
|
text: $model.mapTitle,
|
||||||
|
prompt: "Map title",
|
||||||
|
footer: "The title to show above the map image")
|
||||||
|
OptionalStringPropertyView(
|
||||||
|
title: "Map Image Caption",
|
||||||
|
text: $model.caption,
|
||||||
|
prompt: "Image Caption",
|
||||||
|
footer: "The caption to show below the fullscreen map")
|
||||||
|
OptionalStringPropertyView(
|
||||||
|
title: "Chart Title",
|
||||||
|
text: $model.chartTitle,
|
||||||
|
prompt: "Title",
|
||||||
|
footer: "The title to show above the statistics")
|
||||||
|
Picker(selection: $model.components) {
|
||||||
|
Text("All").tag(RouteViewComponents.all)
|
||||||
|
Text("Only elevation").tag(RouteViewComponents.withoutHeartRate)
|
||||||
|
Text("No heart rate").tag(RouteViewComponents.withoutHeartRate)
|
||||||
|
} label: {
|
||||||
|
Text("")
|
||||||
|
}
|
||||||
|
.pickerStyle(.segmented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
InsertableRoute(model: .init())
|
||||||
|
}
|
@ -10,6 +10,7 @@ struct InsertableItemsView: View {
|
|||||||
InsertableView<InsertableLabels>()
|
InsertableView<InsertableLabels>()
|
||||||
InsertableView<InsertableButtons>()
|
InsertableView<InsertableButtons>()
|
||||||
InsertableView<InsertableLink>()
|
InsertableView<InsertableLink>()
|
||||||
|
InsertableView<InsertableRoute>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,11 @@ struct PageSettingsDetailView: View {
|
|||||||
footer: "The manifest file with the properties of the website when used as a progressive web app",
|
footer: "The manifest file with the properties of the website when used as a progressive web app",
|
||||||
selectedFile: $pageSettings.manifestFile)
|
selectedFile: $pageSettings.manifestFile)
|
||||||
|
|
||||||
|
FilePropertyView(
|
||||||
|
title: "Route Statistics Javascript File",
|
||||||
|
footer: "The JavaScript file containing the logic to compute and animate statistics about workout routes",
|
||||||
|
selectedFile: $pageSettings.routeJsFile)
|
||||||
|
|
||||||
LocalizedPageSettingsView(settings: pageSettings.localized(in: language))
|
LocalizedPageSettingsView(settings: pageSettings.localized(in: language))
|
||||||
.id(language)
|
.id(language)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user