feat: Adds pdf client tests, updates view controller snapshots that have changed.
This commit is contained in:
@@ -93,6 +93,7 @@ let package = Package(
|
|||||||
dependencies: [
|
dependencies: [
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
|
.product(name: "Vapor", package: "vapor"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
@@ -113,6 +114,18 @@ let package = Package(
|
|||||||
.product(name: "Elementary", package: "elementary"),
|
.product(name: "Elementary", package: "elementary"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "PdfClientTests",
|
||||||
|
dependencies: [
|
||||||
|
.target(name: "HTMLSnapshotTesting"),
|
||||||
|
.target(name: "PdfClient"),
|
||||||
|
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
|
||||||
|
]
|
||||||
|
// ,
|
||||||
|
// resources: [
|
||||||
|
// .copy("__Snapshots__")
|
||||||
|
// ]
|
||||||
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "ProjectClient",
|
name: "ProjectClient",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ struct DependenciesMiddleware: AsyncMiddleware {
|
|||||||
// $0.dateFormatter = .liveValue
|
// $0.dateFormatter = .liveValue
|
||||||
$0.viewController = viewController
|
$0.viewController = viewController
|
||||||
$0.pdfClient = .liveValue
|
$0.pdfClient = .liveValue
|
||||||
|
$0.fileClient = .live(fileIO: request.fileio)
|
||||||
} operation: {
|
} operation: {
|
||||||
try await next.respond(to: request)
|
try await next.respond(to: request)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,43 +114,6 @@ extension SiteRoute {
|
|||||||
|
|
||||||
extension DuctSizes: Content {}
|
extension DuctSizes: Content {}
|
||||||
|
|
||||||
// FIX: Move
|
|
||||||
func handlePdf(_ projectID: Project.ID, on request: Request) async throws -> Response {
|
|
||||||
@Dependency(\.projectClient) var projectClient
|
|
||||||
return try await projectClient.generatePdf(projectID, request.fileio)
|
|
||||||
|
|
||||||
// let html = try await projectClient.toHTML(projectID)
|
|
||||||
// let url = "/tmp/\(projectID)"
|
|
||||||
// try await request.fileio.writeFile(.init(string: html.render()), at: "\(url).html")
|
|
||||||
//
|
|
||||||
// let process = Process()
|
|
||||||
// let standardInput = Pipe()
|
|
||||||
// let standardOutput = Pipe()
|
|
||||||
// process.standardInput = standardInput
|
|
||||||
// process.standardOutput = standardOutput
|
|
||||||
// process.executableURL = URL(fileURLWithPath: "/bin/pandoc")
|
|
||||||
// process.arguments = [
|
|
||||||
// "\(url).html",
|
|
||||||
// "--pdf-engine=weasyprint",
|
|
||||||
// "--from=html",
|
|
||||||
// "--css=Public/css/pdf.css",
|
|
||||||
// "-o", "\(url).pdf",
|
|
||||||
// ]
|
|
||||||
// try process.run()
|
|
||||||
// process.waitUntilExit()
|
|
||||||
//
|
|
||||||
// let response = try await request.fileio.asyncStreamFile(at: "\(url).pdf", mediaType: .pdf) { _ in
|
|
||||||
// // Remove files here.
|
|
||||||
// try FileManager.default.removeItem(atPath: "\(url).pdf")
|
|
||||||
// try FileManager.default.removeItem(atPath: "\(url).html")
|
|
||||||
// }
|
|
||||||
// response.headers.replaceOrAdd(name: .contentType, value: "application/octet-stream")
|
|
||||||
// response.headers.replaceOrAdd(
|
|
||||||
// name: .contentDisposition, value: "attachment; filename=Duct-Calc.pdf"
|
|
||||||
// )
|
|
||||||
// return response
|
|
||||||
}
|
|
||||||
|
|
||||||
@Sendable
|
@Sendable
|
||||||
private func siteHandler(
|
private func siteHandler(
|
||||||
request: Request,
|
request: Request,
|
||||||
@@ -165,9 +128,10 @@ private func siteHandler(
|
|||||||
return try await apiController.respond(route, request: request)
|
return try await apiController.respond(route, request: request)
|
||||||
case .health:
|
case .health:
|
||||||
return HTTPStatus.ok
|
return HTTPStatus.ok
|
||||||
// FIX: Move
|
// Generating a pdf return's a `Response` instead of `HTML` like other views, so we
|
||||||
|
// need to handle it seperately.
|
||||||
case .view(.project(.detail(let projectID, .pdf))):
|
case .view(.project(.detail(let projectID, .pdf))):
|
||||||
return try await handlePdf(projectID, on: request)
|
return try await projectClient.generatePdf(projectID)
|
||||||
case .view(let route):
|
case .view(let route):
|
||||||
return try await viewController.respond(route: route, request: request)
|
return try await viewController.respond(route: route, request: request)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import DependenciesMacros
|
import DependenciesMacros
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Vapor
|
||||||
|
|
||||||
extension DependencyValues {
|
extension DependencyValues {
|
||||||
public var fileClient: FileClient {
|
public var fileClient: FileClient {
|
||||||
@@ -11,19 +12,29 @@ extension DependencyValues {
|
|||||||
|
|
||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct FileClient: Sendable {
|
public struct FileClient: Sendable {
|
||||||
|
public typealias OnCompleteHandler = @Sendable () async throws -> Void
|
||||||
|
|
||||||
public var writeFile: @Sendable (String, String) async throws -> Void
|
public var writeFile: @Sendable (String, String) async throws -> Void
|
||||||
public var removeFile: @Sendable (String) async throws -> Void
|
public var removeFile: @Sendable (String) async throws -> Void
|
||||||
|
public var streamFile: @Sendable (String, @escaping OnCompleteHandler) async throws -> Response
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FileClient: DependencyKey {
|
extension FileClient: TestDependencyKey {
|
||||||
public static let testValue = Self()
|
public static let testValue = Self()
|
||||||
|
|
||||||
public static let liveValue = Self(
|
public static func live(fileIO: FileIO) -> Self {
|
||||||
writeFile: { contents, path in
|
.init(
|
||||||
try contents.write(to: URL(fileURLWithPath: path), atomically: true, encoding: .utf8)
|
writeFile: { contents, path in
|
||||||
},
|
try await fileIO.writeFile(ByteBuffer(string: contents), at: path)
|
||||||
removeFile: { path in
|
},
|
||||||
try FileManager.default.removeItem(atPath: path)
|
removeFile: { path in
|
||||||
}
|
try FileManager.default.removeItem(atPath: path)
|
||||||
)
|
},
|
||||||
|
streamFile: { path, onComplete in
|
||||||
|
try await fileIO.asyncStreamFile(at: path) { _ in
|
||||||
|
try await onComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import ManualDCore
|
|||||||
|
|
||||||
extension DependencyValues {
|
extension DependencyValues {
|
||||||
|
|
||||||
|
/// Access the pdf client dependency that can be used to generate pdf's for
|
||||||
|
/// a project.
|
||||||
public var pdfClient: PdfClient {
|
public var pdfClient: PdfClient {
|
||||||
get { self[PdfClient.self] }
|
get { self[PdfClient.self] }
|
||||||
set { self[PdfClient.self] = newValue }
|
set { self[PdfClient.self] = newValue }
|
||||||
@@ -16,9 +18,19 @@ extension DependencyValues {
|
|||||||
|
|
||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct PdfClient: Sendable {
|
public struct PdfClient: Sendable {
|
||||||
|
/// Generate the html used to convert to pdf for a project.
|
||||||
public var html: @Sendable (Request) async throws -> (any HTML & Sendable)
|
public var html: @Sendable (Request) async throws -> (any HTML & Sendable)
|
||||||
|
|
||||||
|
/// Converts the generated html to a pdf.
|
||||||
|
///
|
||||||
|
/// **NOTE:** This is generally not used directly, instead use the overload that accepts a request,
|
||||||
|
/// which generates the html and does the conversion all in one step.
|
||||||
public var generatePdf: @Sendable (Project.ID, any HTML & Sendable) async throws -> Response
|
public var generatePdf: @Sendable (Project.ID, any HTML & Sendable) async throws -> Response
|
||||||
|
|
||||||
|
/// Generate a pdf for the given project request.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - request: The project data used to generate the pdf.
|
||||||
public func generatePdf(request: Request) async throws -> Response {
|
public func generatePdf(request: Request) async throws -> Response {
|
||||||
let html = try await self.html(request)
|
let html = try await self.html(request)
|
||||||
return try await self.generatePdf(request.project.id, html)
|
return try await self.generatePdf(request.project.id, html)
|
||||||
@@ -64,7 +76,7 @@ extension PdfClient: DependencyKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension PdfClient {
|
extension PdfClient {
|
||||||
|
/// Container for the data required to generate a pdf for a given project.
|
||||||
public struct Request: Codable, Equatable, Sendable {
|
public struct Request: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
public let project: Project
|
public let project: Project
|
||||||
|
|||||||
@@ -28,12 +28,7 @@ public struct ProjectClient: Sendable {
|
|||||||
@Sendable (User.ID, Project.Create) async throws -> CreateProjectResponse
|
@Sendable (User.ID, Project.Create) async throws -> CreateProjectResponse
|
||||||
|
|
||||||
public var frictionRate: @Sendable (Project.ID) async throws -> FrictionRateResponse
|
public var frictionRate: @Sendable (Project.ID) async throws -> FrictionRateResponse
|
||||||
|
public var generatePdf: @Sendable (Project.ID) async throws -> Response
|
||||||
// FIX: Name to something to do with generating a pdf, just experimenting now.
|
|
||||||
// public var toMarkdown: @Sendable (Project.ID) async throws -> String
|
|
||||||
// public var toHTML: @Sendable (Project.ID) async throws -> (any HTML & Sendable)
|
|
||||||
|
|
||||||
public var generatePdf: @Sendable (Project.ID, FileIO) async throws -> Response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProjectClient: TestDependencyKey {
|
extension ProjectClient: TestDependencyKey {
|
||||||
|
|||||||
@@ -37,14 +37,18 @@ extension ProjectClient: DependencyKey {
|
|||||||
frictionRate: { projectID in
|
frictionRate: { projectID in
|
||||||
try await manualD.frictionRate(projectID: projectID)
|
try await manualD.frictionRate(projectID: projectID)
|
||||||
},
|
},
|
||||||
generatePdf: { projectID, fileIO in
|
generatePdf: { projectID in
|
||||||
let pdfResponse = try await pdfClient.generatePdf(
|
let pdfResponse = try await pdfClient.generatePdf(
|
||||||
request: database.makePdfRequest(projectID))
|
request: database.makePdfRequest(projectID)
|
||||||
|
)
|
||||||
|
|
||||||
let response = try await fileIO.asyncStreamFile(at: pdfResponse.pdfPath) { _ in
|
let response = try await fileClient.streamFile(
|
||||||
try await fileClient.removeFile(pdfResponse.htmlPath)
|
pdfResponse.pdfPath,
|
||||||
try await fileClient.removeFile(pdfResponse.pdfPath)
|
{
|
||||||
}
|
try await fileClient.removeFile(pdfResponse.htmlPath)
|
||||||
|
try await fileClient.removeFile(pdfResponse.pdfPath)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
response.headers.replaceOrAdd(name: .contentType, value: "application/octet-stream")
|
response.headers.replaceOrAdd(name: .contentType, value: "application/octet-stream")
|
||||||
response.headers.replaceOrAdd(
|
response.headers.replaceOrAdd(
|
||||||
|
|||||||
26
Tests/PdfClientTests/PdfClientTests.swift
Normal file
26
Tests/PdfClientTests/PdfClientTests.swift
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
import HTMLSnapshotTesting
|
||||||
|
import PdfClient
|
||||||
|
import SnapshotTesting
|
||||||
|
import Testing
|
||||||
|
|
||||||
|
@Suite(.snapshots(record: .missing))
|
||||||
|
struct PdfClientTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func html() async throws {
|
||||||
|
|
||||||
|
try await withDependencies {
|
||||||
|
$0.pdfClient = .liveValue
|
||||||
|
$0.uuid = .incrementing
|
||||||
|
$0.date.now = Date(timeIntervalSince1970: 1_234_567_890)
|
||||||
|
} operation: {
|
||||||
|
@Dependency(\.pdfClient) var pdfClient
|
||||||
|
|
||||||
|
let html = try await pdfClient.html(.mock())
|
||||||
|
assertSnapshot(of: html, as: .html)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
583
Tests/PdfClientTests/__Snapshots__/PdfClientTests/html.1.html
Normal file
583
Tests/PdfClientTests/__Snapshots__/PdfClientTests/html.1.html
Normal file
@@ -0,0 +1,583 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Duct Calc</title>
|
||||||
|
<link rel="stylesheet" href="/css/pdf.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<h2>Project</h2>
|
||||||
|
<div class="flex">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="label">Name</td>
|
||||||
|
<td>Testy McTestface</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label">Address</td>
|
||||||
|
<td>
|
||||||
|
<p>
|
||||||
|
1234 Sesame Street
|
||||||
|
<br>
|
||||||
|
Monroe, OH 55555
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table></table>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<div class="flex">
|
||||||
|
<h2>Equipment</h2>
|
||||||
|
<h2>Friction Rate</h2>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="container">
|
||||||
|
<div class="table-container">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-green">
|
||||||
|
<th>Equipment</th>
|
||||||
|
<th class="justify-end">Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Static Pressure</td>
|
||||||
|
<td class="justify-end">0.5</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Heating CFM</td>
|
||||||
|
<td class="justify-end">900</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Cooling CFM</td>
|
||||||
|
<td class="justify-end">1,000</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="table-container">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-green">
|
||||||
|
<th>Friction Rate</th>
|
||||||
|
<th class="justify-end">Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>evaporator-coil</td>
|
||||||
|
<td class="justify-end">0.2</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>filter</td>
|
||||||
|
<td class="justify-end">0.1</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>supply-outlet</td>
|
||||||
|
<td class="justify-end">0.03</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>return-grille</td>
|
||||||
|
<td class="justify-end">0.03</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>balancing-damper</td>
|
||||||
|
<td class="justify-end">0.03</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Duct Sizes</h2>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-green">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Dsn CFM</th>
|
||||||
|
<th>Round Size</th>
|
||||||
|
<th>Velocity</th>
|
||||||
|
<th>Final Size</th>
|
||||||
|
<th>Flex Size</th>
|
||||||
|
<th>Height</th>
|
||||||
|
<th>Width</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Bed-1</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Entry</td>
|
||||||
|
<td>88</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Entry</td>
|
||||||
|
<td>88</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Kitchen</td>
|
||||||
|
<td>95</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Kitchen</td>
|
||||||
|
<td>95</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Living Room</td>
|
||||||
|
<td>127</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Living Room</td>
|
||||||
|
<td>127</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Master</td>
|
||||||
|
<td>87</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Master</td>
|
||||||
|
<td>87</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Supply Trunk / Run Outs</h2>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead class="bg-green">
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Dsn CFM</th>
|
||||||
|
<th>Round Size</th>
|
||||||
|
<th>Velocity</th>
|
||||||
|
<th>Final Size</th>
|
||||||
|
<th>Flex Size</th>
|
||||||
|
<th>Height</th>
|
||||||
|
<th>Width</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>1,000</td>
|
||||||
|
<td>18</td>
|
||||||
|
<td>987</td>
|
||||||
|
<td>20</td>
|
||||||
|
<td>20</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Return Trunk / Run Outs</h2>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead class="bg-green">
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Dsn CFM</th>
|
||||||
|
<th>Round Size</th>
|
||||||
|
<th>Velocity</th>
|
||||||
|
<th>Final Size</th>
|
||||||
|
<th>Flex Size</th>
|
||||||
|
<th>Height</th>
|
||||||
|
<th>Width</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>1,000</td>
|
||||||
|
<td>18</td>
|
||||||
|
<td>987</td>
|
||||||
|
<td>20</td>
|
||||||
|
<td>20</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Total Equivalent Lengths</h2>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-green">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Straight Lengths</th>
|
||||||
|
<th>Groups</th>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Supply - 1</td>
|
||||||
|
<td>supply</td>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
<li>10</li>
|
||||||
|
<li>25</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="effectiveLengthGroupHeader">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Length</th>
|
||||||
|
<th>Quantity</th>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>1-a</td>
|
||||||
|
<td>20</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>20</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>2-b</td>
|
||||||
|
<td>30</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>30</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>3-a</td>
|
||||||
|
<td>10</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>10</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>12-a</td>
|
||||||
|
<td>10</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>10</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
<td>105</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Return - 1</td>
|
||||||
|
<td>return</td>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
<li>10</li>
|
||||||
|
<li>20</li>
|
||||||
|
<li>5</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="effectiveLengthGroupHeader">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Length</th>
|
||||||
|
<th>Quantity</th>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>5-a</td>
|
||||||
|
<td>10</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>10</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>6-a</td>
|
||||||
|
<td>15</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>15</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>7-a</td>
|
||||||
|
<td>20</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>20</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
<td>80</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Register Detail</h2>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-green">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Heating BTU</th>
|
||||||
|
<th>Cooling BTU</th>
|
||||||
|
<th>Heating CFM</th>
|
||||||
|
<th>Cooling CFM</th>
|
||||||
|
<th>Design CFM</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Bed-1</td>
|
||||||
|
<td>3,913</td>
|
||||||
|
<td>2,472</td>
|
||||||
|
<td>83</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>92</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Entry</td>
|
||||||
|
<td>4,142</td>
|
||||||
|
<td>1,458</td>
|
||||||
|
<td>88</td>
|
||||||
|
<td>54</td>
|
||||||
|
<td>88</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Entry</td>
|
||||||
|
<td>4,142</td>
|
||||||
|
<td>1,458</td>
|
||||||
|
<td>88</td>
|
||||||
|
<td>54</td>
|
||||||
|
<td>88</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>3,262</td>
|
||||||
|
<td>2,482</td>
|
||||||
|
<td>69</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>92</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>3,262</td>
|
||||||
|
<td>2,482</td>
|
||||||
|
<td>69</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>92</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>3,262</td>
|
||||||
|
<td>2,482</td>
|
||||||
|
<td>69</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>92</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Kitchen</td>
|
||||||
|
<td>2,259</td>
|
||||||
|
<td>2,548</td>
|
||||||
|
<td>48</td>
|
||||||
|
<td>95</td>
|
||||||
|
<td>95</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Kitchen</td>
|
||||||
|
<td>2,259</td>
|
||||||
|
<td>2,548</td>
|
||||||
|
<td>48</td>
|
||||||
|
<td>95</td>
|
||||||
|
<td>95</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Living Room</td>
|
||||||
|
<td>3,776</td>
|
||||||
|
<td>3,414</td>
|
||||||
|
<td>80</td>
|
||||||
|
<td>127</td>
|
||||||
|
<td>127</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Living Room</td>
|
||||||
|
<td>3,776</td>
|
||||||
|
<td>3,414</td>
|
||||||
|
<td>80</td>
|
||||||
|
<td>127</td>
|
||||||
|
<td>127</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Master</td>
|
||||||
|
<td>4,101</td>
|
||||||
|
<td>1,038</td>
|
||||||
|
<td>87</td>
|
||||||
|
<td>39</td>
|
||||||
|
<td>87</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Master</td>
|
||||||
|
<td>4,101</td>
|
||||||
|
<td>1,038</td>
|
||||||
|
<td>87</td>
|
||||||
|
<td>39</td>
|
||||||
|
<td>87</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Room Detail</h2>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-green">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Heating BTU</th>
|
||||||
|
<th>Cooling Total BTU</th>
|
||||||
|
<th>Cooling Sensible BTU</th>
|
||||||
|
<th>Register Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Bed-1</td>
|
||||||
|
<td>3,913</td>
|
||||||
|
<td>2,472</td>
|
||||||
|
<td>2,052</td>
|
||||||
|
<td>1</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Entry</td>
|
||||||
|
<td>8,284</td>
|
||||||
|
<td>2,916</td>
|
||||||
|
<td>2,420</td>
|
||||||
|
<td>2</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>9,785</td>
|
||||||
|
<td>7,446</td>
|
||||||
|
<td>6,180</td>
|
||||||
|
<td>3</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Kitchen</td>
|
||||||
|
<td>4,518</td>
|
||||||
|
<td>5,096</td>
|
||||||
|
<td>4,230</td>
|
||||||
|
<td>2</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Living Room</td>
|
||||||
|
<td>7,553</td>
|
||||||
|
<td>6,829</td>
|
||||||
|
<td>5,668</td>
|
||||||
|
<td>2</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Master</td>
|
||||||
|
<td>8,202</td>
|
||||||
|
<td>2,076</td>
|
||||||
|
<td>1,723</td>
|
||||||
|
<td>2</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label">Totals</td>
|
||||||
|
<td class="heating label">42,255</td>
|
||||||
|
<td class="coolingTotal label">26,835</td>
|
||||||
|
<td class="coolingSensible label">22,273</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -11,7 +11,7 @@ import SnapshotTesting
|
|||||||
import Testing
|
import Testing
|
||||||
import ViewController
|
import ViewController
|
||||||
|
|
||||||
@Suite(.snapshots(record: .missing))
|
@Suite(.snapshots(record: .failed))
|
||||||
struct ViewControllerTests {
|
struct ViewControllerTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -16,8 +16,10 @@
|
|||||||
<meta content="1024" name="og:image:height">
|
<meta content="1024" name="og:image:height">
|
||||||
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
<link rel="stylesheet" href="/css/output.css">
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
|||||||
@@ -16,8 +16,10 @@
|
|||||||
<meta content="1024" name="og:image:height">
|
<meta content="1024" name="og:image:height">
|
||||||
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
<link rel="stylesheet" href="/css/output.css">
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
|||||||
@@ -16,8 +16,10 @@
|
|||||||
<meta content="1024" name="og:image:height">
|
<meta content="1024" name="og:image:height">
|
||||||
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
<link rel="stylesheet" href="/css/output.css">
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
|||||||
@@ -16,8 +16,10 @@
|
|||||||
<meta content="1024" name="og:image:height">
|
<meta content="1024" name="og:image:height">
|
||||||
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
<link rel="stylesheet" href="/css/output.css">
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
|||||||
@@ -16,8 +16,10 @@
|
|||||||
<meta content="1024" name="og:image:height">
|
<meta content="1024" name="og:image:height">
|
||||||
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
<link rel="stylesheet" href="/css/output.css">
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
|||||||
@@ -16,8 +16,10 @@
|
|||||||
<meta content="1024" name="og:image:height">
|
<meta content="1024" name="og:image:height">
|
||||||
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
<link rel="stylesheet" href="/css/output.css">
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
|||||||
@@ -16,8 +16,10 @@
|
|||||||
<meta content="1024" name="og:image:height">
|
<meta content="1024" name="og:image:height">
|
||||||
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
<link rel="stylesheet" href="/css/output.css">
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
@@ -55,7 +57,9 @@ p-6 w-full">
|
|||||||
<p>Must complete all the previous sections to display duct sizing calculations.</p>
|
<p>Must complete all the previous sections to display duct sizing calculations.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
PDF<a class="btn btn-primary" href="/projects/00000000-0000-0000-0000-000000000000/pdf"></a>
|
<div>
|
||||||
|
<button class="btn btn-primary" hx-get="/projects/00000000-0000-0000-0000-000000000000/pdf" hx-ext="htmx-download" hx-swap="none" hx-indicator=".htmx-indicator"><span>PDF</span><span class="loading loading-spinner loading-lg htmx-indicator"></span></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-zebra text-lg">
|
<table class="table table-zebra text-lg">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -16,8 +16,10 @@
|
|||||||
<meta content="1024" name="og:image:height">
|
<meta content="1024" name="og:image:height">
|
||||||
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
<link rel="stylesheet" href="/css/output.css">
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
|||||||
@@ -16,8 +16,10 @@
|
|||||||
<meta content="1024" name="og:image:height">
|
<meta content="1024" name="og:image:height">
|
||||||
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
<link rel="stylesheet" href="/css/output.css">
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
|||||||
@@ -16,8 +16,10 @@
|
|||||||
<meta content="1024" name="og:image:height">
|
<meta content="1024" name="og:image:height">
|
||||||
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
<link rel="stylesheet" href="/css/output.css">
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
|||||||
Reference in New Issue
Block a user