Compare commits
2 Commits
6602c4a8b5
...
1d155546ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
1d155546ae
|
|||
|
6c6045b4a6
|
@@ -25,7 +25,9 @@ extension SiteRoute.Api.ProjectRoute {
|
|||||||
|
|
||||||
switch self {
|
switch self {
|
||||||
case .create(let request):
|
case .create(let request):
|
||||||
return try await database.projects.create(request)
|
// return try await database.projects.create(request)
|
||||||
|
// FIX:
|
||||||
|
fatalError()
|
||||||
case .delete(let id):
|
case .delete(let id):
|
||||||
try await database.projects.delete(id)
|
try await database.projects.delete(id)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ extension ViewController {
|
|||||||
route: route,
|
route: route,
|
||||||
isHtmxRequest: request.isHtmxRequest,
|
isHtmxRequest: request.isHtmxRequest,
|
||||||
logger: request.logger,
|
logger: request.logger,
|
||||||
// authenticate: { request.session.authenticate($0) },
|
authenticateUser: { request.session.authenticate($0) },
|
||||||
// currentUser: {
|
currentUser: {
|
||||||
// try request.auth.require(User.self)
|
try request.auth.require(User.self)
|
||||||
// }
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return AnyHTMLResponse(value: html)
|
return AnyHTMLResponse(value: html)
|
||||||
|
|||||||
24
Sources/App/Middleware/ViewRoute+middleware.swift
Normal file
24
Sources/App/Middleware/ViewRoute+middleware.swift
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import DatabaseClient
|
||||||
|
import Fluent
|
||||||
|
import ManualDCore
|
||||||
|
import Vapor
|
||||||
|
|
||||||
|
private let viewRouteMiddleware: [any Middleware] = [
|
||||||
|
UserPasswordAuthenticator(),
|
||||||
|
UserSessionAuthenticator(),
|
||||||
|
User.redirectMiddleware(path: "/login"),
|
||||||
|
]
|
||||||
|
|
||||||
|
extension SiteRoute.View {
|
||||||
|
var middleware: [any Middleware]? {
|
||||||
|
switch self {
|
||||||
|
case .project,
|
||||||
|
.frictionRate,
|
||||||
|
.effectiveLength,
|
||||||
|
.room:
|
||||||
|
return viewRouteMiddleware
|
||||||
|
case .login, .signup:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -100,15 +100,14 @@ private func addCommands(to app: Application) {
|
|||||||
extension SiteRoute {
|
extension SiteRoute {
|
||||||
|
|
||||||
fileprivate func middleware() -> [any Middleware]? {
|
fileprivate func middleware() -> [any Middleware]? {
|
||||||
|
switch self {
|
||||||
|
case .api:
|
||||||
return nil
|
return nil
|
||||||
// switch self {
|
case .health:
|
||||||
// case .api(let route):
|
return nil
|
||||||
// return route.middleware
|
case .view(let route):
|
||||||
// // case .health:
|
return route.middleware
|
||||||
// // return nil
|
}
|
||||||
// case .view(let route):
|
|
||||||
// return route.middleware
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public struct DatabaseClient: Sendable {
|
|||||||
public var equipment: Equipment
|
public var equipment: Equipment
|
||||||
public var componentLoss: ComponentLoss
|
public var componentLoss: ComponentLoss
|
||||||
public var effectiveLength: EffectiveLengthClient
|
public var effectiveLength: EffectiveLengthClient
|
||||||
|
public var users: Users
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DatabaseClient: TestDependencyKey {
|
extension DatabaseClient: TestDependencyKey {
|
||||||
@@ -27,7 +28,8 @@ extension DatabaseClient: TestDependencyKey {
|
|||||||
rooms: .testValue,
|
rooms: .testValue,
|
||||||
equipment: .testValue,
|
equipment: .testValue,
|
||||||
componentLoss: .testValue,
|
componentLoss: .testValue,
|
||||||
effectiveLength: .testValue
|
effectiveLength: .testValue,
|
||||||
|
users: .testValue
|
||||||
)
|
)
|
||||||
|
|
||||||
public static func live(database: any Database) -> Self {
|
public static func live(database: any Database) -> Self {
|
||||||
@@ -37,7 +39,8 @@ extension DatabaseClient: TestDependencyKey {
|
|||||||
rooms: .live(database: database),
|
rooms: .live(database: database),
|
||||||
equipment: .live(database: database),
|
equipment: .live(database: database),
|
||||||
componentLoss: .live(database: database),
|
componentLoss: .live(database: database),
|
||||||
effectiveLength: .live(database: database)
|
effectiveLength: .live(database: database),
|
||||||
|
users: .live(database: database)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,21 +7,20 @@ import ManualDCore
|
|||||||
extension DatabaseClient {
|
extension DatabaseClient {
|
||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct Projects: Sendable {
|
public struct Projects: Sendable {
|
||||||
public var create: @Sendable (Project.Create) async throws -> Project
|
public var create: @Sendable (User.ID, Project.Create) async throws -> Project
|
||||||
public var delete: @Sendable (Project.ID) async throws -> Void
|
public var delete: @Sendable (Project.ID) async throws -> Void
|
||||||
public var get: @Sendable (Project.ID) async throws -> Project?
|
public var get: @Sendable (Project.ID) async throws -> Project?
|
||||||
|
public var fetch: @Sendable (User.ID, PageRequest) async throws -> Page<Project>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DatabaseClient.Projects: TestDependencyKey {
|
extension DatabaseClient.Projects: TestDependencyKey {
|
||||||
public static let testValue = Self()
|
public static let testValue = Self()
|
||||||
}
|
|
||||||
|
|
||||||
extension DatabaseClient.Projects {
|
|
||||||
public static func live(database: any Database) -> Self {
|
public static func live(database: any Database) -> Self {
|
||||||
.init(
|
.init(
|
||||||
create: { request in
|
create: { userID, request in
|
||||||
let model = try request.toModel()
|
let model = try request.toModel(userID: userID)
|
||||||
try await model.save(on: database)
|
try await model.save(on: database)
|
||||||
return try model.toDTO()
|
return try model.toDTO()
|
||||||
},
|
},
|
||||||
@@ -33,6 +32,14 @@ extension DatabaseClient.Projects {
|
|||||||
},
|
},
|
||||||
get: { id in
|
get: { id in
|
||||||
try await ProjectModel.find(id, on: database).map { try $0.toDTO() }
|
try await ProjectModel.find(id, on: database).map { try $0.toDTO() }
|
||||||
|
},
|
||||||
|
fetch: { userID, request in
|
||||||
|
try await ProjectModel.query(on: database)
|
||||||
|
.sort(\.$createdAt, .descending)
|
||||||
|
.with(\.$user)
|
||||||
|
.filter(\.$user.$id == userID)
|
||||||
|
.paginate(request)
|
||||||
|
.map { try $0.toDTO() }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -40,14 +47,15 @@ extension DatabaseClient.Projects {
|
|||||||
|
|
||||||
extension Project.Create {
|
extension Project.Create {
|
||||||
|
|
||||||
func toModel() throws -> ProjectModel {
|
func toModel(userID: User.ID) throws -> ProjectModel {
|
||||||
try validate()
|
try validate()
|
||||||
return .init(
|
return .init(
|
||||||
name: name,
|
name: name,
|
||||||
streetAddress: streetAddress,
|
streetAddress: streetAddress,
|
||||||
city: city,
|
city: city,
|
||||||
state: state,
|
state: state,
|
||||||
zipCode: zipCode
|
zipCode: zipCode,
|
||||||
|
userID: userID
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +92,8 @@ extension Project {
|
|||||||
.field("zipCode", .string, .required)
|
.field("zipCode", .string, .required)
|
||||||
.field("createdAt", .datetime)
|
.field("createdAt", .datetime)
|
||||||
.field("updatedAt", .datetime)
|
.field("updatedAt", .datetime)
|
||||||
.unique(on: "name")
|
.field("userID", .uuid, .required, .references(UserModel.schema, "id"))
|
||||||
|
.unique(on: "userID", "name")
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +135,9 @@ final class ProjectModel: Model, @unchecked Sendable {
|
|||||||
@Children(for: \.$project)
|
@Children(for: \.$project)
|
||||||
var componentLosses: [ComponentLossModel]
|
var componentLosses: [ComponentLossModel]
|
||||||
|
|
||||||
|
@Parent(key: "userID")
|
||||||
|
var user: UserModel
|
||||||
|
|
||||||
init() {}
|
init() {}
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@@ -135,6 +147,7 @@ final class ProjectModel: Model, @unchecked Sendable {
|
|||||||
city: String,
|
city: String,
|
||||||
state: String,
|
state: String,
|
||||||
zipCode: String,
|
zipCode: String,
|
||||||
|
userID: User.ID,
|
||||||
createdAt: Date? = nil,
|
createdAt: Date? = nil,
|
||||||
updatedAt: Date? = nil
|
updatedAt: Date? = nil
|
||||||
) {
|
) {
|
||||||
@@ -145,6 +158,7 @@ final class ProjectModel: Model, @unchecked Sendable {
|
|||||||
self.city = city
|
self.city = city
|
||||||
self.state = state
|
self.state = state
|
||||||
self.zipCode = zipCode
|
self.zipCode = zipCode
|
||||||
|
$user.id = userID
|
||||||
self.createdAt = createdAt
|
self.createdAt = createdAt
|
||||||
self.updatedAt = updatedAt
|
self.updatedAt = updatedAt
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ extension User {
|
|||||||
.field("username", .string, .required)
|
.field("username", .string, .required)
|
||||||
.field("email", .string, .required)
|
.field("email", .string, .required)
|
||||||
.field("password_hash", .string, .required)
|
.field("password_hash", .string, .required)
|
||||||
.field("created_at", .datetime)
|
.field("createdAt", .datetime)
|
||||||
.field("updated_at", .datetime)
|
.field("updatedAt", .datetime)
|
||||||
.unique(on: "email", "username")
|
.unique(on: "email", "username")
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ extension Project {
|
|||||||
streetAddress: String,
|
streetAddress: String,
|
||||||
city: String,
|
city: String,
|
||||||
state: String,
|
state: String,
|
||||||
zipCode: String
|
zipCode: String,
|
||||||
) {
|
) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.streetAddress = streetAddress
|
self.streetAddress = streetAddress
|
||||||
|
|||||||
@@ -7,13 +7,21 @@ extension SiteRoute {
|
|||||||
///
|
///
|
||||||
/// The routes return html.
|
/// The routes return html.
|
||||||
public enum View: Equatable, Sendable {
|
public enum View: Equatable, Sendable {
|
||||||
|
case login(LoginRoute)
|
||||||
|
case signup(SignupRoute)
|
||||||
case project(ProjectRoute)
|
case project(ProjectRoute)
|
||||||
case room(RoomRoute)
|
case room(RoomRoute)
|
||||||
case frictionRate(FrictionRateRoute)
|
case frictionRate(FrictionRateRoute)
|
||||||
case effectiveLength(EffectiveLengthRoute)
|
case effectiveLength(EffectiveLengthRoute)
|
||||||
case user(UserRoute)
|
// case user(UserRoute)
|
||||||
|
|
||||||
public static let router = OneOf {
|
public static let router = OneOf {
|
||||||
|
Route(.case(Self.login)) {
|
||||||
|
SiteRoute.View.LoginRoute.router
|
||||||
|
}
|
||||||
|
Route(.case(Self.signup)) {
|
||||||
|
SiteRoute.View.SignupRoute.router
|
||||||
|
}
|
||||||
Route(.case(Self.project)) {
|
Route(.case(Self.project)) {
|
||||||
SiteRoute.View.ProjectRoute.router
|
SiteRoute.View.ProjectRoute.router
|
||||||
}
|
}
|
||||||
@@ -26,9 +34,9 @@ extension SiteRoute {
|
|||||||
Route(.case(Self.effectiveLength)) {
|
Route(.case(Self.effectiveLength)) {
|
||||||
SiteRoute.View.EffectiveLengthRoute.router
|
SiteRoute.View.EffectiveLengthRoute.router
|
||||||
}
|
}
|
||||||
Route(.case(Self.user)) {
|
// Route(.case(Self.user)) {
|
||||||
SiteRoute.View.UserRoute.router
|
// SiteRoute.View.UserRoute.router
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,6 +46,7 @@ extension SiteRoute.View {
|
|||||||
case create(Project.Create)
|
case create(Project.Create)
|
||||||
case form(dismiss: Bool = false)
|
case form(dismiss: Bool = false)
|
||||||
case index
|
case index
|
||||||
|
case page(page: Int = 1, limit: Int = 25)
|
||||||
|
|
||||||
static let rootPath = "projects"
|
static let rootPath = "projects"
|
||||||
|
|
||||||
@@ -70,6 +79,17 @@ extension SiteRoute.View {
|
|||||||
Path { rootPath }
|
Path { rootPath }
|
||||||
Method.get
|
Method.get
|
||||||
}
|
}
|
||||||
|
Route(.case(Self.page(page:limit:))) {
|
||||||
|
Path {
|
||||||
|
rootPath
|
||||||
|
"page"
|
||||||
|
}
|
||||||
|
Method.get
|
||||||
|
Query {
|
||||||
|
Field("page", default: 1) { Int.parser() }
|
||||||
|
Field("limit", default: 25) { Int.parser() }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,25 +198,21 @@ extension SiteRoute.View.EffectiveLengthRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extension SiteRoute.View {
|
||||||
|
// public enum UserRoute: Equatable, Sendable {
|
||||||
|
// case signup(Signup)
|
||||||
|
//
|
||||||
|
// public static let router = OneOf {
|
||||||
|
// Route(.case(Self.signup)) {
|
||||||
|
// SiteRoute.View.UserRoute.Signup.router
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
extension SiteRoute.View {
|
extension SiteRoute.View {
|
||||||
public enum UserRoute: Equatable, Sendable {
|
|
||||||
case login(Login)
|
|
||||||
case signup(Signup)
|
|
||||||
|
|
||||||
public static let router = OneOf {
|
public enum LoginRoute: Equatable, Sendable {
|
||||||
Route(.case(Self.login)) {
|
|
||||||
SiteRoute.View.UserRoute.Login.router
|
|
||||||
}
|
|
||||||
Route(.case(Self.signup)) {
|
|
||||||
SiteRoute.View.UserRoute.Signup.router
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SiteRoute.View.UserRoute {
|
|
||||||
|
|
||||||
public enum Login: Equatable, Sendable {
|
|
||||||
case index
|
case index
|
||||||
case submit(User.Login)
|
case submit(User.Login)
|
||||||
|
|
||||||
@@ -220,8 +236,11 @@ extension SiteRoute.View.UserRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum Signup: Equatable, Sendable {
|
extension SiteRoute.View {
|
||||||
|
|
||||||
|
public enum SignupRoute: Equatable, Sendable {
|
||||||
case index
|
case index
|
||||||
case submit(User.Create)
|
case submit(User.Create)
|
||||||
|
|
||||||
|
|||||||
9
Sources/Styleguide/ElementaryExtensions.swift
Normal file
9
Sources/Styleguide/ElementaryExtensions.swift
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import Elementary
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
extension HTMLAttribute where Tag: HTMLTrait.Attributes.href {
|
||||||
|
|
||||||
|
public static func href(route: SiteRoute.View) -> Self {
|
||||||
|
href(SiteRoute.View.router.path(for: route))
|
||||||
|
}
|
||||||
|
}
|
||||||
26
Sources/Styleguide/Indicator.swift
Normal file
26
Sources/Styleguide/Indicator.swift
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import Elementary
|
||||||
|
|
||||||
|
public struct Indicator: HTML, Sendable {
|
||||||
|
|
||||||
|
let size: Size
|
||||||
|
|
||||||
|
public init(size: Size = .lg) {
|
||||||
|
self.size = size
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some HTML<HTMLTag.span> {
|
||||||
|
span(.class("loading loading-spinner \(size.class) htmx-indicator")) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Size: String, Equatable, Sendable {
|
||||||
|
case xs
|
||||||
|
case sm
|
||||||
|
case md
|
||||||
|
case lg
|
||||||
|
case xl
|
||||||
|
|
||||||
|
var `class`: String {
|
||||||
|
"loading-\(rawValue)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,10 @@ public typealias AnySendableHTML = (any HTML & Sendable)
|
|||||||
|
|
||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct ViewController: Sendable {
|
public struct ViewController: Sendable {
|
||||||
|
|
||||||
|
public typealias AuthenticateHandler = @Sendable (User) -> Void
|
||||||
|
public typealias CurrentUserHandler = @Sendable () throws -> User
|
||||||
|
|
||||||
public var view: @Sendable (Request) async throws -> AnySendableHTML
|
public var view: @Sendable (Request) async throws -> AnySendableHTML
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,15 +29,25 @@ extension ViewController {
|
|||||||
public let route: SiteRoute.View
|
public let route: SiteRoute.View
|
||||||
public let isHtmxRequest: Bool
|
public let isHtmxRequest: Bool
|
||||||
public let logger: Logger
|
public let logger: Logger
|
||||||
|
public let authenticateUser: AuthenticateHandler
|
||||||
|
public let currentUser: CurrentUserHandler
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
route: SiteRoute.View,
|
route: SiteRoute.View,
|
||||||
isHtmxRequest: Bool,
|
isHtmxRequest: Bool,
|
||||||
logger: Logger
|
logger: Logger,
|
||||||
|
authenticateUser: @escaping AuthenticateHandler,
|
||||||
|
currentUser: @escaping CurrentUserHandler
|
||||||
) {
|
) {
|
||||||
self.route = route
|
self.route = route
|
||||||
self.isHtmxRequest = isHtmxRequest
|
self.isHtmxRequest = isHtmxRequest
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
|
self.authenticateUser = authenticateUser
|
||||||
|
self.currentUser = currentUser
|
||||||
|
}
|
||||||
|
|
||||||
|
func authenticate(_ user: User) {
|
||||||
|
self.authenticateUser(user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,33 @@
|
|||||||
|
import DatabaseClient
|
||||||
|
import Dependencies
|
||||||
import Elementary
|
import Elementary
|
||||||
|
import Foundation
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
extension ViewController.Request {
|
extension ViewController.Request {
|
||||||
|
|
||||||
func render() async throws -> AnySendableHTML {
|
func render() async throws -> AnySendableHTML {
|
||||||
|
|
||||||
|
@Dependency(\.database) var database
|
||||||
|
|
||||||
switch route {
|
switch route {
|
||||||
|
case .login(let route):
|
||||||
|
switch route {
|
||||||
|
case .index:
|
||||||
|
return try await _render(isHtmxRequest: isHtmxRequest, showSidebar: false) {
|
||||||
|
LoginForm()
|
||||||
|
}
|
||||||
|
case .submit(let login):
|
||||||
|
let token = try await database.users.login(login)
|
||||||
|
let user = try await database.users.get(token.userID)!
|
||||||
|
authenticate(user)
|
||||||
|
let projects = try await database.projects.fetch(user.id, .init(page: 1, per: 25))
|
||||||
|
return try await _render(isHtmxRequest: isHtmxRequest, showSidebar: false) {
|
||||||
|
ProjectsTable(userID: user.id, projects: projects)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .signup(let route):
|
||||||
|
return try await route.renderView(isHtmxRequest: isHtmxRequest)
|
||||||
case .project(let route):
|
case .project(let route):
|
||||||
return try await route.renderView(isHtmxRequest: isHtmxRequest)
|
return try await route.renderView(isHtmxRequest: isHtmxRequest)
|
||||||
case .room(let route):
|
case .room(let route):
|
||||||
@@ -13,27 +36,46 @@ extension ViewController.Request {
|
|||||||
return try await route.renderView(isHtmxRequest: isHtmxRequest)
|
return try await route.renderView(isHtmxRequest: isHtmxRequest)
|
||||||
case .effectiveLength(let route):
|
case .effectiveLength(let route):
|
||||||
return try await route.renderView(isHtmxRequest: isHtmxRequest)
|
return try await route.renderView(isHtmxRequest: isHtmxRequest)
|
||||||
case .user(let route):
|
// case .user(let route):
|
||||||
return try await route.renderView(isHtmxRequest: isHtmxRequest)
|
// return try await route.renderView(isHtmxRequest: isHtmxRequest)
|
||||||
default:
|
default:
|
||||||
// FIX: FIX
|
// FIX: FIX
|
||||||
return mainPage
|
return try await _render(isHtmxRequest: false) {
|
||||||
|
div { "Fix me!" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SiteRoute.View.ProjectRoute {
|
extension SiteRoute.View.ProjectRoute {
|
||||||
|
|
||||||
|
private var shouldShowSidebar: Bool {
|
||||||
|
switch self {
|
||||||
|
case .index, .page: return false
|
||||||
|
default: return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML {
|
func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML {
|
||||||
|
@Dependency(\.database.projects) var projects
|
||||||
|
|
||||||
|
return try await _render(
|
||||||
|
isHtmxRequest: isHtmxRequest,
|
||||||
|
showSidebar: shouldShowSidebar
|
||||||
|
) {
|
||||||
switch self {
|
switch self {
|
||||||
case .index:
|
case .index:
|
||||||
return MainPage(active: .projects) {
|
// ProjectView(project: .mock)
|
||||||
ProjectView(project: .mock)
|
let page = try await projects.fetch(UUID(0), .init(page: 1, per: 25))
|
||||||
}
|
ProjectsTable(userID: UUID(0), projects: page)
|
||||||
|
case .page(let page, let limit):
|
||||||
|
let page = try await projects.fetch(UUID(0), .init(page: page, per: limit))
|
||||||
|
ProjectsTable.Rows(projects: page)
|
||||||
case .form(let dismiss):
|
case .form(let dismiss):
|
||||||
return ProjectForm(dismiss: dismiss)
|
ProjectForm(dismiss: dismiss)
|
||||||
|
|
||||||
case .create:
|
case .create:
|
||||||
return mainPage
|
div { "Fix me!" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,7 +86,7 @@ extension SiteRoute.View.RoomRoute {
|
|||||||
case .form(let dismiss):
|
case .form(let dismiss):
|
||||||
return RoomForm(dismiss: dismiss)
|
return RoomForm(dismiss: dismiss)
|
||||||
case .index:
|
case .index:
|
||||||
return MainPage(active: .rooms) {
|
return try await _render(isHtmxRequest: isHtmxRequest, active: .rooms) {
|
||||||
RoomsView(rooms: Room.mocks)
|
RoomsView(rooms: Room.mocks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,7 +97,7 @@ extension SiteRoute.View.FrictionRateRoute {
|
|||||||
func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML {
|
func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML {
|
||||||
switch self {
|
switch self {
|
||||||
case .index:
|
case .index:
|
||||||
return MainPage(active: .frictionRate) {
|
return try await _render(isHtmxRequest: isHtmxRequest, active: .frictionRate) {
|
||||||
FrictionRateView()
|
FrictionRateView()
|
||||||
}
|
}
|
||||||
case .form(let type, let dismiss):
|
case .form(let type, let dismiss):
|
||||||
@@ -86,7 +128,7 @@ extension SiteRoute.View.EffectiveLengthRoute {
|
|||||||
func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML {
|
func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML {
|
||||||
switch self {
|
switch self {
|
||||||
case .index:
|
case .index:
|
||||||
return MainPage(active: .effectiveLength) {
|
return try await _render(isHtmxRequest: isHtmxRequest, active: .effectiveLength) {
|
||||||
EffectiveLengthsView(effectiveLengths: EffectiveLength.mocks)
|
EffectiveLengthsView(effectiveLengths: EffectiveLength.mocks)
|
||||||
}
|
}
|
||||||
case .form(let dismiss):
|
case .form(let dismiss):
|
||||||
@@ -103,49 +145,66 @@ extension SiteRoute.View.EffectiveLengthRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SiteRoute.View.UserRoute {
|
extension SiteRoute.View.SignupRoute {
|
||||||
|
|
||||||
func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML {
|
func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML {
|
||||||
|
@Dependency(\.database.users) var users
|
||||||
|
|
||||||
switch self {
|
switch self {
|
||||||
case .login(.index):
|
case .index:
|
||||||
return MainPage(active: .projects, showSidebar: false) {
|
return try await _render(isHtmxRequest: isHtmxRequest, showSidebar: false) {
|
||||||
LoginForm()
|
|
||||||
}
|
|
||||||
case .signup(.index):
|
|
||||||
return MainPage(active: .projects, showSidebar: false) {
|
|
||||||
LoginForm(style: .signup)
|
LoginForm(style: .signup)
|
||||||
}
|
}
|
||||||
default:
|
case .submit(let request):
|
||||||
return div { "Fix Me!" }
|
_ = try await users.create(request)
|
||||||
|
// FIX: We should just login the new user at this point.
|
||||||
|
return try await _render(isHtmxRequest: isHtmxRequest, showSidebar: false) {
|
||||||
|
LoginForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
// default:
|
||||||
|
// return div { "Fix Me!" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let mainPage: AnySendableHTML = {
|
// extension SiteRoute.View.LoginRoute {
|
||||||
MainPage(active: .projects) {
|
// func renderView(on req: ViewController.Request) async throws -> AnySendableHTML {
|
||||||
div {
|
//
|
||||||
h1 { "It works!" }
|
// @Dependency(\.database) var database
|
||||||
}
|
//
|
||||||
}
|
// return try await _render(isHtmxRequest: req.isHtmxRequest, showSidebar: false) {
|
||||||
}()
|
// switch self {
|
||||||
|
// case .index:
|
||||||
|
// LoginForm()
|
||||||
|
// case .submit(let login):
|
||||||
|
// // FIX:
|
||||||
|
// // div { "Logged in Success! Fix me!" }
|
||||||
|
// let token = try await database.users.login(login)
|
||||||
|
// let user = try await database.users.get(token.userID)!
|
||||||
|
// _ = req.authenticate(user)
|
||||||
|
// // req.authenticate(user)
|
||||||
|
// let page = try await database.projects.fetch(user.id, .init(page: 1, per: 25))
|
||||||
|
// ProjectsTable(userID: user.id, projects: page)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
@Sendable
|
private func _render<C: HTML>(
|
||||||
private func render<C: HTML>(
|
isHtmxRequest: Bool,
|
||||||
_ mainPage: (C) async throws -> AnySendableHTML,
|
active activeTab: Sidebar.ActiveTab = .projects,
|
||||||
_ isHtmxRequest: Bool,
|
showSidebar: Bool = true,
|
||||||
@HTMLBuilder html: () -> C
|
@HTMLBuilder inner: () async throws -> C
|
||||||
) async rethrows -> AnySendableHTML where C: Sendable {
|
) async throws -> AnySendableHTML where C: Sendable {
|
||||||
guard isHtmxRequest else {
|
let inner = try await inner()
|
||||||
return try await mainPage(html())
|
if isHtmxRequest {
|
||||||
|
return inner
|
||||||
}
|
}
|
||||||
return html()
|
return MainPage(
|
||||||
|
active: activeTab,
|
||||||
|
showSidebar: showSidebar
|
||||||
|
) {
|
||||||
|
inner
|
||||||
}
|
}
|
||||||
|
|
||||||
@Sendable
|
|
||||||
private func render<C: HTML>(
|
|
||||||
_ mainPage: (C) async throws -> AnySendableHTML,
|
|
||||||
_ isHtmxRequest: Bool,
|
|
||||||
_ html: @autoclosure @escaping () -> C
|
|
||||||
) async rethrows -> AnySendableHTML where C: Sendable {
|
|
||||||
try await render(mainPage, isHtmxRequest) { html() }
|
|
||||||
}
|
}
|
||||||
|
|||||||
80
Sources/ViewController/Views/Project/ProjectsTable.swift
Normal file
80
Sources/ViewController/Views/Project/ProjectsTable.swift
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import Elementary
|
||||||
|
import ElementaryHTMX
|
||||||
|
import Fluent
|
||||||
|
import ManualDCore
|
||||||
|
import Styleguide
|
||||||
|
import Vapor
|
||||||
|
|
||||||
|
struct ProjectsTable: HTML, Sendable {
|
||||||
|
|
||||||
|
let userID: User.ID
|
||||||
|
let projects: Page<Project>
|
||||||
|
|
||||||
|
init(userID: User.ID, projects: Page<Project>) {
|
||||||
|
self.userID = userID
|
||||||
|
self.projects = projects
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some HTML {
|
||||||
|
div {
|
||||||
|
Row {
|
||||||
|
h1(.class("text-2xl font-bold")) { "Projects" }
|
||||||
|
div(
|
||||||
|
.class("tooltip tooltip-left"),
|
||||||
|
.data("tip", value: "Add project")
|
||||||
|
) {
|
||||||
|
button(
|
||||||
|
.class("btn btn-primary w-[40px] text-2xl")
|
||||||
|
) {
|
||||||
|
"+"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.attributes(.class("pb-6"))
|
||||||
|
|
||||||
|
div(.class("overflow-x-auto rounded-box border")) {
|
||||||
|
table(.class("table table-zebra")) {
|
||||||
|
thead {
|
||||||
|
tr {
|
||||||
|
th { Label("Date") }
|
||||||
|
th { Label("Name") }
|
||||||
|
th { Label("Address") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
Rows(projects: projects)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ProjectsTable {
|
||||||
|
struct Rows: HTML, Sendable {
|
||||||
|
let projects: Page<Project>
|
||||||
|
|
||||||
|
var body: some HTML {
|
||||||
|
for project in projects.items {
|
||||||
|
tr(.id("\(project.id)")) {
|
||||||
|
td { "\(project.createdAt)" }
|
||||||
|
td { "\(project.name)" }
|
||||||
|
td { "\(project.streetAddress)" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Have a row that when revealed fetches the next page,
|
||||||
|
// if there are more pages left.
|
||||||
|
if projects.metadata.pageCount > projects.metadata.page {
|
||||||
|
tr(
|
||||||
|
.hx.get(route: .project(.page(page: projects.metadata.page + 1, limit: 25))),
|
||||||
|
.hx.trigger(.event(.revealed)),
|
||||||
|
.hx.swap(.outerHTML),
|
||||||
|
.hx.target("this"),
|
||||||
|
.hx.indicator("next .htmx-indicator")
|
||||||
|
) {
|
||||||
|
Indicator(size: .lg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,13 @@ struct LoginForm: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
form {
|
div(
|
||||||
|
.id("loginForm"),
|
||||||
|
.class("flex items-center justify-center")
|
||||||
|
) {
|
||||||
|
form(
|
||||||
|
.method(.post)
|
||||||
|
) {
|
||||||
fieldset(.class("fieldset bg-base-200 border-base-300 rounded-box w-xl border p-4")) {
|
fieldset(.class("fieldset bg-base-200 border-base-300 rounded-box w-xl border p-4")) {
|
||||||
legend(.class("fieldset-legend")) { style.title }
|
legend(.class("fieldset-legend")) { style.title }
|
||||||
|
|
||||||
@@ -20,6 +26,7 @@ struct LoginForm: HTML, Sendable {
|
|||||||
SVG(.user)
|
SVG(.user)
|
||||||
input(
|
input(
|
||||||
.type(.text), .required, .placeholder("Username"),
|
.type(.text), .required, .placeholder("Username"),
|
||||||
|
.name("username"), .id("username"),
|
||||||
.minlength("3"), .pattern(.username)
|
.minlength("3"), .pattern(.username)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -33,7 +40,8 @@ struct LoginForm: HTML, Sendable {
|
|||||||
label(.class("input validator w-full")) {
|
label(.class("input validator w-full")) {
|
||||||
SVG(.email)
|
SVG(.email)
|
||||||
input(
|
input(
|
||||||
.type(.email), .placeholder("Email"), .required
|
.type(.email), .placeholder("Email"), .required,
|
||||||
|
.name("email"), .id("email"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
div(.class("validator-hint hidden")) { "Enter valid email address." }
|
div(.class("validator-hint hidden")) { "Enter valid email address." }
|
||||||
@@ -42,7 +50,8 @@ struct LoginForm: HTML, Sendable {
|
|||||||
SVG(.key)
|
SVG(.key)
|
||||||
input(
|
input(
|
||||||
.type(.password), .placeholder("Password"), .required,
|
.type(.password), .placeholder("Password"), .required,
|
||||||
.pattern(.password), .minlength("8")
|
.pattern(.password), .minlength("8"),
|
||||||
|
.name("password"), .id("password"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +60,8 @@ struct LoginForm: HTML, Sendable {
|
|||||||
SVG(.key)
|
SVG(.key)
|
||||||
input(
|
input(
|
||||||
.type(.password), .placeholder("Confirm Password"), .required,
|
.type(.password), .placeholder("Confirm Password"), .required,
|
||||||
.pattern(.password), .minlength("8")
|
.pattern(.password), .minlength("8"),
|
||||||
|
.name("confirmPassword"), .id("confirmPassword"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,78 +78,16 @@ struct LoginForm: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button(.class("btn btn-neutral mt-4")) { style.title }
|
button(.class("btn btn-secondary mt-4")) { style.title }
|
||||||
|
a(
|
||||||
|
.class("btn btn-link mt-4"),
|
||||||
|
.href(route: style == .signup ? .login(.index) : .signup(.index))
|
||||||
|
) {
|
||||||
|
style == .login ? "Sign Up" : "Login"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// div(.class("flex items-center justify-center")) {
|
|
||||||
// div(.class("w-full mx-auto")) {
|
|
||||||
// h1(.class("text-2xl font-bold")) { style.title }
|
|
||||||
// form(
|
|
||||||
// .class("w-full h-screen")
|
|
||||||
// ) {
|
|
||||||
// fieldset(.class("fieldset w-full")) {
|
|
||||||
// legend(.class("fieldset-legend")) { "Email" }
|
|
||||||
// label(.class("input validator")) {
|
|
||||||
// SVG(.email)
|
|
||||||
// input(
|
|
||||||
// .type(.email), .placeholder("mail@site.com"), .required,
|
|
||||||
// .autofocus
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// div(.class("validator-hint hidden")) { "Enter valid email address." }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if style == .signup {
|
|
||||||
// fieldset(.class("fieldset")) {
|
|
||||||
// legend(.class("fieldset-legend")) { "Name" }
|
|
||||||
// label(.class("input validator")) {
|
|
||||||
// input(
|
|
||||||
// .type(.text), .placeholder("Username"), .required,
|
|
||||||
// .init(name: "pattern", value: "[A-Za-z][A-Za-z0-9\\-]*"),
|
|
||||||
// .init(name: "minlength", value: "3")
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// div(.class("validator-hint hidden")) { "Enter valid email address." }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fieldset(.class("fieldset")) {
|
|
||||||
// legend(.class("fieldset-legend")) { "Password" }
|
|
||||||
// label(.class("input validator")) {
|
|
||||||
// SVG(.key)
|
|
||||||
// input(
|
|
||||||
// .type(.password), .placeholder("Password"), .required,
|
|
||||||
// .init(name: "pattern", value: "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"),
|
|
||||||
// .init(name: "minlength", value: "8")
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// if style == .signup {
|
|
||||||
// label(.class("input validator")) {
|
|
||||||
// SVG(.key)
|
|
||||||
// input(
|
|
||||||
// .type(.password), .placeholder("Confirm Password"), .required,
|
|
||||||
// .init(name: "pattern", value: "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"),
|
|
||||||
// .init(name: "minlength", value: "8")
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// div(.class("validator-hint hidden")) {
|
|
||||||
// p {
|
|
||||||
// "Must be more than 8 characters, including"
|
|
||||||
// br()
|
|
||||||
// "At least one number"
|
|
||||||
// br()
|
|
||||||
// "At least one lowercase letter"
|
|
||||||
// br()
|
|
||||||
// "At least one uppercase letter"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// SubmitButton(title: style.title)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
justfile
2
justfile
@@ -7,7 +7,7 @@ run-css:
|
|||||||
@./tailwindcss -i input.css -o output.css --watch
|
@./tailwindcss -i input.css -o output.css --watch
|
||||||
|
|
||||||
run:
|
run:
|
||||||
@swift run App
|
@SWIFT_BACTRACE=enable=no swift run App
|
||||||
|
|
||||||
build-docker:
|
build-docker:
|
||||||
@podman build -f docker/Dockerfile.dev -t {{docker_image}}:dev .
|
@podman build -f docker/Dockerfile.dev -t {{docker_image}}:dev .
|
||||||
|
|||||||
504
output.css
504
output.css
@@ -16,6 +16,7 @@
|
|||||||
--color-indigo-600: oklch(51.1% 0.262 276.966);
|
--color-indigo-600: oklch(51.1% 0.262 276.966);
|
||||||
--color-slate-300: oklch(86.9% 0.022 252.894);
|
--color-slate-300: oklch(86.9% 0.022 252.894);
|
||||||
--color-slate-900: oklch(20.8% 0.042 265.755);
|
--color-slate-900: oklch(20.8% 0.042 265.755);
|
||||||
|
--color-gray-100: oklch(96.7% 0.003 264.542);
|
||||||
--color-gray-200: oklch(92.8% 0.006 264.531);
|
--color-gray-200: oklch(92.8% 0.006 264.531);
|
||||||
--color-gray-300: oklch(87.2% 0.01 258.338);
|
--color-gray-300: oklch(87.2% 0.01 258.338);
|
||||||
--color-gray-400: oklch(70.7% 0.022 261.325);
|
--color-gray-400: oklch(70.7% 0.022 261.325);
|
||||||
@@ -23,6 +24,16 @@
|
|||||||
--color-black: #000;
|
--color-black: #000;
|
||||||
--color-white: #fff;
|
--color-white: #fff;
|
||||||
--spacing: 0.25rem;
|
--spacing: 0.25rem;
|
||||||
|
--container-xs: 20rem;
|
||||||
|
--container-xl: 36rem;
|
||||||
|
--container-2xl: 42rem;
|
||||||
|
--container-7xl: 80rem;
|
||||||
|
--text-xs: 0.75rem;
|
||||||
|
--text-xs--line-height: calc(1 / 0.75);
|
||||||
|
--text-sm: 0.875rem;
|
||||||
|
--text-sm--line-height: calc(1.25 / 0.875);
|
||||||
|
--text-base: 1rem;
|
||||||
|
--text-base--line-height: calc(1.5 / 1);
|
||||||
--text-xl: 1.25rem;
|
--text-xl: 1.25rem;
|
||||||
--text-xl--line-height: calc(1.75 / 1.25);
|
--text-xl--line-height: calc(1.75 / 1.25);
|
||||||
--text-2xl: 1.5rem;
|
--text-2xl: 1.5rem;
|
||||||
@@ -31,9 +42,21 @@
|
|||||||
--text-3xl--line-height: calc(2.25 / 1.875);
|
--text-3xl--line-height: calc(2.25 / 1.875);
|
||||||
--text-4xl: 2.25rem;
|
--text-4xl: 2.25rem;
|
||||||
--text-4xl--line-height: calc(2.5 / 2.25);
|
--text-4xl--line-height: calc(2.5 / 2.25);
|
||||||
|
--text-5xl: 3rem;
|
||||||
|
--text-5xl--line-height: 1;
|
||||||
|
--font-weight-medium: 500;
|
||||||
|
--font-weight-semibold: 600;
|
||||||
--font-weight-bold: 700;
|
--font-weight-bold: 700;
|
||||||
|
--leading-tight: 1.25;
|
||||||
|
--radius-sm: 0.25rem;
|
||||||
--radius-md: 0.375rem;
|
--radius-md: 0.375rem;
|
||||||
--radius-lg: 0.5rem;
|
--radius-lg: 0.5rem;
|
||||||
|
--radius-xl: 0.75rem;
|
||||||
|
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||||
|
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
--default-transition-duration: 150ms;
|
||||||
|
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
--default-font-family: var(--font-sans);
|
--default-font-family: var(--font-sans);
|
||||||
--default-mono-font-family: var(--font-mono);
|
--default-mono-font-family: var(--font-mono);
|
||||||
}
|
}
|
||||||
@@ -387,6 +410,185 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.loading {
|
||||||
|
@layer daisyui.l1.l2.l3 {
|
||||||
|
pointer-events: none;
|
||||||
|
display: inline-block;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
background-color: currentcolor;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: calc(var(--size-selector, 0.25rem) * 6);
|
||||||
|
mask-size: 100%;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.validator-hint {
|
||||||
|
@layer daisyui.l1.l2.l3 {
|
||||||
|
visibility: hidden;
|
||||||
|
margin-top: calc(0.25rem * 2);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.validator {
|
||||||
|
@layer daisyui.l1.l2.l3 {
|
||||||
|
&:user-valid, &:has(:user-valid) {
|
||||||
|
&, &:focus, &:checked, &[aria-checked="true"], &:focus-within {
|
||||||
|
--input-color: var(--color-success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:user-invalid, &:has(:user-invalid), &[aria-invalid]:not([aria-invalid="false"]), &:has([aria-invalid]:not([aria-invalid="false"])) {
|
||||||
|
&, &:focus, &:checked, &[aria-checked="true"], &:focus-within {
|
||||||
|
--input-color: var(--color-error);
|
||||||
|
}
|
||||||
|
& ~ .validator-hint {
|
||||||
|
visibility: visible;
|
||||||
|
color: var(--color-error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:user-invalid, &:has(:user-invalid), &[aria-invalid]:not([aria-invalid="false"]), &:has([aria-invalid]:not([aria-invalid="false"])) {
|
||||||
|
& ~ .validator-hint {
|
||||||
|
display: revert-layer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toggle {
|
||||||
|
@layer daisyui.l1.l2.l3 {
|
||||||
|
border: var(--border) solid currentColor;
|
||||||
|
color: var(--input-color);
|
||||||
|
position: relative;
|
||||||
|
display: inline-grid;
|
||||||
|
flex-shrink: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
place-content: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
grid-template-columns: 0fr 1fr 1fr;
|
||||||
|
--radius-selector-max: calc(
|
||||||
|
var(--radius-selector) + var(--radius-selector) + var(--radius-selector)
|
||||||
|
);
|
||||||
|
border-radius: calc( var(--radius-selector) + min(var(--toggle-p), var(--radius-selector-max)) + min(var(--border), var(--radius-selector-max)) );
|
||||||
|
padding: var(--toggle-p);
|
||||||
|
box-shadow: 0 1px currentColor inset;
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
box-shadow: 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000) inset;
|
||||||
|
}
|
||||||
|
transition: color 0.3s, grid-template-columns 0.2s;
|
||||||
|
--input-color: var(--color-base-content);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
--input-color: color-mix(in oklab, var(--color-base-content) 50%, #0000);
|
||||||
|
}
|
||||||
|
--toggle-p: calc(var(--size) * 0.125);
|
||||||
|
--size: calc(var(--size-selector, 0.25rem) * 6);
|
||||||
|
width: calc((var(--size) * 2) - (var(--border) + var(--toggle-p)) * 2);
|
||||||
|
height: var(--size);
|
||||||
|
> * {
|
||||||
|
z-index: 1;
|
||||||
|
grid-column: span 1 / span 1;
|
||||||
|
grid-column-start: 2;
|
||||||
|
grid-row-start: 1;
|
||||||
|
height: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: calc(0.25rem * 0.5);
|
||||||
|
transition: opacity 0.2s, rotate 0.4s;
|
||||||
|
border: none;
|
||||||
|
&:focus {
|
||||||
|
--tw-outline-style: none;
|
||||||
|
outline-style: none;
|
||||||
|
@media (forced-colors: active) {
|
||||||
|
outline: 2px solid transparent;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:nth-child(2) {
|
||||||
|
color: var(--color-base-100);
|
||||||
|
rotate: 0deg;
|
||||||
|
}
|
||||||
|
&:nth-child(3) {
|
||||||
|
color: var(--color-base-100);
|
||||||
|
opacity: 0%;
|
||||||
|
rotate: -15deg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:has(:checked) {
|
||||||
|
> :nth-child(2) {
|
||||||
|
opacity: 0%;
|
||||||
|
rotate: 15deg;
|
||||||
|
}
|
||||||
|
> :nth-child(3) {
|
||||||
|
opacity: 100%;
|
||||||
|
rotate: 0deg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:before {
|
||||||
|
position: relative;
|
||||||
|
inset-inline-start: calc(0.25rem * 0);
|
||||||
|
grid-column-start: 2;
|
||||||
|
grid-row-start: 1;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: var(--radius-selector);
|
||||||
|
background-color: currentcolor;
|
||||||
|
translate: 0;
|
||||||
|
--tw-content: "";
|
||||||
|
content: var(--tw-content);
|
||||||
|
transition: background-color 0.1s, translate 0.2s, inset-inline-start 0.2s;
|
||||||
|
box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px currentColor;
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000);
|
||||||
|
}
|
||||||
|
background-size: auto, calc(var(--noise) * 100%);
|
||||||
|
background-image: none, var(--fx-noise);
|
||||||
|
}
|
||||||
|
@media (forced-colors: active) {
|
||||||
|
&:before {
|
||||||
|
outline-style: var(--tw-outline-style);
|
||||||
|
outline-width: 1px;
|
||||||
|
outline-offset: calc(1px * -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media print {
|
||||||
|
&:before {
|
||||||
|
outline: 0.25rem solid;
|
||||||
|
outline-offset: -1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:focus-visible, &:has(:focus-visible) {
|
||||||
|
outline: 2px solid currentColor;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
&:checked, &[aria-checked="true"], &:has(> input:checked) {
|
||||||
|
grid-template-columns: 1fr 1fr 0fr;
|
||||||
|
background-color: var(--color-base-100);
|
||||||
|
--input-color: var(--color-base-content);
|
||||||
|
&:before {
|
||||||
|
background-color: currentcolor;
|
||||||
|
}
|
||||||
|
@starting-style {
|
||||||
|
&:before {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:indeterminate {
|
||||||
|
grid-template-columns: 0.5fr 1fr 0.5fr;
|
||||||
|
}
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 30%;
|
||||||
|
&:before {
|
||||||
|
background-color: transparent;
|
||||||
|
border: var(--border) solid currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.input {
|
.input {
|
||||||
@layer daisyui.l1.l2.l3 {
|
@layer daisyui.l1.l2.l3 {
|
||||||
cursor: text;
|
cursor: text;
|
||||||
@@ -509,6 +711,75 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.table {
|
||||||
|
@layer daisyui.l1.l2.l3 {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: separate;
|
||||||
|
--tw-border-spacing-x: calc(0.25rem * 0);
|
||||||
|
--tw-border-spacing-y: calc(0.25rem * 0);
|
||||||
|
border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
|
||||||
|
border-radius: var(--radius-box);
|
||||||
|
text-align: left;
|
||||||
|
&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
tr.row-hover {
|
||||||
|
&, &:nth-child(even) {
|
||||||
|
&:hover {
|
||||||
|
@media (hover: hover) {
|
||||||
|
background-color: var(--color-base-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:where(th, td) {
|
||||||
|
padding-inline: calc(0.25rem * 4);
|
||||||
|
padding-block: calc(0.25rem * 3);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
:where(thead, tfoot) {
|
||||||
|
white-space: nowrap;
|
||||||
|
color: var(--color-base-content);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
color: color-mix(in oklab, var(--color-base-content) 60%, transparent);
|
||||||
|
}
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
:where(tfoot tr:first-child :is(td, th)) {
|
||||||
|
border-top: var(--border) solid var(--color-base-content);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
border-top: var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:where(.table-pin-rows thead tr) {
|
||||||
|
position: sticky;
|
||||||
|
top: calc(0.25rem * 0);
|
||||||
|
z-index: 1;
|
||||||
|
background-color: var(--color-base-100);
|
||||||
|
}
|
||||||
|
:where(.table-pin-rows tfoot tr) {
|
||||||
|
position: sticky;
|
||||||
|
bottom: calc(0.25rem * 0);
|
||||||
|
z-index: 1;
|
||||||
|
background-color: var(--color-base-100);
|
||||||
|
}
|
||||||
|
:where(.table-pin-cols tr th) {
|
||||||
|
position: sticky;
|
||||||
|
right: calc(0.25rem * 0);
|
||||||
|
left: calc(0.25rem * 0);
|
||||||
|
background-color: var(--color-base-100);
|
||||||
|
}
|
||||||
|
:where(thead tr :is(td, th), tbody tr:not(:last-child) :is(td, th)) {
|
||||||
|
border-bottom: var(--border) solid var(--color-base-content);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
border-bottom: var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.fixed {
|
.fixed {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
}
|
}
|
||||||
@@ -518,6 +789,18 @@
|
|||||||
.sticky {
|
.sticky {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
}
|
}
|
||||||
|
.tooltip-left {
|
||||||
|
@layer daisyui.l1.l2 {
|
||||||
|
> .tooltip-content, &[data-tip]:before {
|
||||||
|
transform: translateX(calc(var(--tt-pos, 0.25rem) - 0.25rem)) translateY(-50%);
|
||||||
|
inset: 50% var(--tt-off) auto auto;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
transform: translateX(var(--tt-pos, 0.25rem)) translateY(-50%) rotate(-90deg);
|
||||||
|
inset: 50% calc(var(--tt-tail) + 1px) auto auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.top-0 {
|
.top-0 {
|
||||||
top: calc(var(--spacing) * 0);
|
top: calc(var(--spacing) * 0);
|
||||||
}
|
}
|
||||||
@@ -530,9 +813,6 @@
|
|||||||
.z-50 {
|
.z-50 {
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
.col-span-5 {
|
|
||||||
grid-column: span 5 / span 5;
|
|
||||||
}
|
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@media (width >= 40rem) {
|
@media (width >= 40rem) {
|
||||||
@@ -554,9 +834,6 @@
|
|||||||
.m-4 {
|
.m-4 {
|
||||||
margin: calc(var(--spacing) * 4);
|
margin: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
.m-10 {
|
|
||||||
margin: calc(var(--spacing) * 10);
|
|
||||||
}
|
|
||||||
.filter {
|
.filter {
|
||||||
@layer daisyui.l1.l2.l3 {
|
@layer daisyui.l1.l2.l3 {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -644,8 +921,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.mb-2 {
|
.mt-4 {
|
||||||
margin-bottom: calc(var(--spacing) * 2);
|
margin-top: calc(var(--spacing) * 4);
|
||||||
|
}
|
||||||
|
.fieldset-legend {
|
||||||
|
@layer daisyui.l1.l2.l3 {
|
||||||
|
margin-bottom: calc(0.25rem * -1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: calc(0.25rem * 2);
|
||||||
|
padding-block: calc(0.25rem * 2);
|
||||||
|
color: var(--color-base-content);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.status {
|
.status {
|
||||||
@layer daisyui.l1.l2.l3 {
|
@layer daisyui.l1.l2.l3 {
|
||||||
@@ -672,36 +961,74 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.badge {
|
||||||
|
@layer daisyui.l1.l2.l3 {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: calc(0.25rem * 2);
|
||||||
|
border-radius: var(--radius-selector);
|
||||||
|
vertical-align: middle;
|
||||||
|
color: var(--badge-fg);
|
||||||
|
border: var(--border) solid var(--badge-color, var(--color-base-200));
|
||||||
|
font-size: 0.875rem;
|
||||||
|
width: fit-content;
|
||||||
|
background-size: auto, calc(var(--noise) * 100%);
|
||||||
|
background-image: none, var(--fx-noise);
|
||||||
|
background-color: var(--badge-bg);
|
||||||
|
--badge-bg: var(--badge-color, var(--color-base-100));
|
||||||
|
--badge-fg: var(--color-base-content);
|
||||||
|
--size: calc(var(--size-selector, 0.25rem) * 6);
|
||||||
|
height: var(--size);
|
||||||
|
padding-inline: calc(var(--size) / 2 - var(--border));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fieldset {
|
||||||
|
@layer daisyui.l1.l2.l3 {
|
||||||
|
display: grid;
|
||||||
|
gap: calc(0.25rem * 1.5);
|
||||||
|
padding-block: calc(0.25rem * 1);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-auto-rows: max-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
.contents {
|
.contents {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
.grid {
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.table {
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
.h-\[1em\] {
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
.h-screen {
|
.h-screen {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
.w-1\/2 {
|
.w-1\/2 {
|
||||||
width: calc(1/2 * 100%);
|
width: calc(1/2 * 100%);
|
||||||
}
|
}
|
||||||
|
.w-\[40px\] {
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
.w-full {
|
.w-full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.w-xl {
|
||||||
|
width: var(--container-xl);
|
||||||
|
}
|
||||||
.max-w-\[280px\] {
|
.max-w-\[280px\] {
|
||||||
max-width: 280px;
|
max-width: 280px;
|
||||||
}
|
}
|
||||||
.flex-none {
|
.flex-none {
|
||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
.grid-cols-5 {
|
|
||||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
.flex-col {
|
.flex-col {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -714,6 +1041,9 @@
|
|||||||
.justify-between {
|
.justify-between {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
.justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
.gap-4 {
|
.gap-4 {
|
||||||
gap: calc(var(--spacing) * 4);
|
gap: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
@@ -745,6 +1075,15 @@
|
|||||||
margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)));
|
margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.overflow-x-auto {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.rounded-box {
|
||||||
|
border-radius: var(--radius-box);
|
||||||
|
}
|
||||||
|
.rounded-box {
|
||||||
|
border-radius: var(--radius-box);
|
||||||
|
}
|
||||||
.rounded-lg {
|
.rounded-lg {
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
}
|
}
|
||||||
@@ -767,14 +1106,48 @@
|
|||||||
border-bottom-style: var(--tw-border-style);
|
border-bottom-style: var(--tw-border-style);
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
}
|
}
|
||||||
|
.badge-outline {
|
||||||
|
@layer daisyui.l1.l2 {
|
||||||
|
color: var(--badge-color);
|
||||||
|
--badge-bg: #0000;
|
||||||
|
background-image: none;
|
||||||
|
border-color: currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.border-base-300 {
|
||||||
|
border-color: var(--color-base-300);
|
||||||
|
}
|
||||||
.border-gray-200 {
|
.border-gray-200 {
|
||||||
border-color: var(--color-gray-200);
|
border-color: var(--color-gray-200);
|
||||||
}
|
}
|
||||||
.border-gray-400 {
|
.border-gray-400 {
|
||||||
border-color: var(--color-gray-400);
|
border-color: var(--color-gray-400);
|
||||||
}
|
}
|
||||||
.bg-blue-400 {
|
.table-zebra {
|
||||||
background-color: var(--color-blue-400);
|
@layer daisyui.l1.l2 {
|
||||||
|
tbody {
|
||||||
|
tr {
|
||||||
|
&:where(:nth-child(even)) {
|
||||||
|
background-color: var(--color-base-200);
|
||||||
|
:where(.table-pin-cols tr th) {
|
||||||
|
background-color: var(--color-base-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.row-hover {
|
||||||
|
&, &:where(:nth-child(even)) {
|
||||||
|
&:hover {
|
||||||
|
@media (hover: hover) {
|
||||||
|
background-color: var(--color-base-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.bg-base-200 {
|
||||||
|
background-color: var(--color-base-200);
|
||||||
}
|
}
|
||||||
.bg-blue-500 {
|
.bg-blue-500 {
|
||||||
background-color: var(--color-blue-500);
|
background-color: var(--color-blue-500);
|
||||||
@@ -782,15 +1155,17 @@
|
|||||||
.bg-gray-200 {
|
.bg-gray-200 {
|
||||||
background-color: var(--color-gray-200);
|
background-color: var(--color-gray-200);
|
||||||
}
|
}
|
||||||
.bg-green-400 {
|
|
||||||
background-color: var(--color-green-400);
|
|
||||||
}
|
|
||||||
.bg-red-500 {
|
.bg-red-500 {
|
||||||
background-color: var(--color-red-500);
|
background-color: var(--color-red-500);
|
||||||
}
|
}
|
||||||
.bg-white {
|
.bg-white {
|
||||||
background-color: var(--color-white);
|
background-color: var(--color-white);
|
||||||
}
|
}
|
||||||
|
.loading-spinner {
|
||||||
|
@layer daisyui.l1.l2 {
|
||||||
|
mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
}
|
||||||
.p-4 {
|
.p-4 {
|
||||||
padding: calc(var(--spacing) * 4);
|
padding: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
@@ -800,6 +1175,9 @@
|
|||||||
.px-4 {
|
.px-4 {
|
||||||
padding-inline: calc(var(--spacing) * 4);
|
padding-inline: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
|
.px-6 {
|
||||||
|
padding-inline: calc(var(--spacing) * 6);
|
||||||
|
}
|
||||||
.py-1\.5 {
|
.py-1\.5 {
|
||||||
padding-block: calc(var(--spacing) * 1.5);
|
padding-block: calc(var(--spacing) * 1.5);
|
||||||
}
|
}
|
||||||
@@ -809,6 +1187,9 @@
|
|||||||
.py-4 {
|
.py-4 {
|
||||||
padding-block: calc(var(--spacing) * 4);
|
padding-block: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
|
.py-10 {
|
||||||
|
padding-block: calc(var(--spacing) * 10);
|
||||||
|
}
|
||||||
.ps-2 {
|
.ps-2 {
|
||||||
padding-inline-start: calc(var(--spacing) * 2);
|
padding-inline-start: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
@@ -837,12 +1218,18 @@
|
|||||||
font-size: var(--text-xl);
|
font-size: var(--text-xl);
|
||||||
line-height: var(--tw-leading, var(--text-xl--line-height));
|
line-height: var(--tw-leading, var(--text-xl--line-height));
|
||||||
}
|
}
|
||||||
|
.badge-xl {
|
||||||
|
@layer daisyui.l1.l2 {
|
||||||
|
--size: calc(var(--size-selector, 0.25rem) * 8);
|
||||||
|
font-size: 1.125rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
.font-bold {
|
.font-bold {
|
||||||
--tw-font-weight: var(--font-weight-bold);
|
--tw-font-weight: var(--font-weight-bold);
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
}
|
}
|
||||||
.text-blue-400 {
|
.text-error {
|
||||||
color: var(--color-blue-400);
|
color: var(--color-error);
|
||||||
}
|
}
|
||||||
.text-gray-400 {
|
.text-gray-400 {
|
||||||
color: var(--color-gray-400);
|
color: var(--color-gray-400);
|
||||||
@@ -850,18 +1237,47 @@
|
|||||||
.text-gray-800 {
|
.text-gray-800 {
|
||||||
color: var(--color-gray-800);
|
color: var(--color-gray-800);
|
||||||
}
|
}
|
||||||
.text-green-400 {
|
.text-info {
|
||||||
color: var(--color-green-400);
|
color: var(--color-info);
|
||||||
}
|
|
||||||
.text-red-500 {
|
|
||||||
color: var(--color-red-500);
|
|
||||||
}
|
}
|
||||||
.text-slate-900 {
|
.text-slate-900 {
|
||||||
color: var(--color-slate-900);
|
color: var(--color-slate-900);
|
||||||
}
|
}
|
||||||
|
.text-success {
|
||||||
|
color: var(--color-success);
|
||||||
|
}
|
||||||
.text-white {
|
.text-white {
|
||||||
color: var(--color-white);
|
color: var(--color-white);
|
||||||
}
|
}
|
||||||
|
.lowercase {
|
||||||
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
|
.uppercase {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.btn-link {
|
||||||
|
.prose :where(&):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||||
|
text-decoration-line: none;
|
||||||
|
}
|
||||||
|
@layer daisyui.l1 {
|
||||||
|
text-decoration-line: underline;
|
||||||
|
outline-color: currentcolor;
|
||||||
|
--btn-border: #0000;
|
||||||
|
--btn-bg: #0000;
|
||||||
|
--btn-noise: none;
|
||||||
|
--btn-shadow: "";
|
||||||
|
&:not(.btn-disabled, .btn:disabled, .btn[disabled]) {
|
||||||
|
--btn-fg: var(--btn-color, var(--color-primary));
|
||||||
|
}
|
||||||
|
&:is(.btn-active, :hover, :active:focus, :focus-visible) {
|
||||||
|
--btn-border: #0000;
|
||||||
|
--btn-bg: #0000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.opacity-50 {
|
||||||
|
opacity: 50%;
|
||||||
|
}
|
||||||
.shadow-lg {
|
.shadow-lg {
|
||||||
--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
@@ -879,24 +1295,22 @@
|
|||||||
.filter {
|
.filter {
|
||||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||||
}
|
}
|
||||||
.btn-outline {
|
.badge-error {
|
||||||
@layer daisyui.l1 {
|
@layer daisyui.l1.l2 {
|
||||||
&:not( .btn-active, :hover, :active:focus, :focus-visible, input:checked:not(.filter .btn), :disabled, [disabled], .btn-disabled ) {
|
--badge-color: var(--color-error);
|
||||||
--btn-shadow: "";
|
--badge-fg: var(--color-error-content);
|
||||||
--btn-bg: #0000;
|
|
||||||
--btn-fg: var(--btn-color);
|
|
||||||
--btn-border: var(--btn-color);
|
|
||||||
--btn-noise: none;
|
|
||||||
}
|
|
||||||
@media (hover: none) {
|
|
||||||
&:not(.btn-active, :active, :focus-visible, input:checked:not(.filter .btn)):hover {
|
|
||||||
--btn-shadow: "";
|
|
||||||
--btn-bg: #0000;
|
|
||||||
--btn-fg: var(--btn-color);
|
|
||||||
--btn-border: var(--btn-color);
|
|
||||||
--btn-noise: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.badge-info {
|
||||||
|
@layer daisyui.l1.l2 {
|
||||||
|
--badge-color: var(--color-info);
|
||||||
|
--badge-fg: var(--color-info-content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.badge-success {
|
||||||
|
@layer daisyui.l1.l2 {
|
||||||
|
--badge-color: var(--color-success);
|
||||||
|
--badge-fg: var(--color-success-content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
@@ -905,10 +1319,10 @@
|
|||||||
--btn-fg: var(--color-primary-content);
|
--btn-fg: var(--color-primary-content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.btn-success {
|
.btn-secondary {
|
||||||
@layer daisyui.l1.l2.l3 {
|
@layer daisyui.l1.l2.l3 {
|
||||||
--btn-color: var(--color-success);
|
--btn-color: var(--color-secondary);
|
||||||
--btn-fg: var(--color-success-content);
|
--btn-fg: var(--color-secondary-content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.invalid\:border-red-500 {
|
.invalid\:border-red-500 {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
touch .build/browser-dev-sync
|
touch .build/browser-dev-sync
|
||||||
# browser-sync start --proxy localhost:8080 --ws -w --no-notify &
|
browser-sync start --proxy localhost:8080 --ws &
|
||||||
watchexec -w Sources -e .swift -r 'swift build --product App && touch .build/browser-dev-sync' &
|
watchexec -w Sources -e .swift -r 'swift build --product App && touch .build/browser-dev-sync' &
|
||||||
watchexec -w .build/browser-dev-sync -r 'swift run App'
|
watchexec -w .build/browser-dev-sync -r 'swift run App'
|
||||||
|
|||||||
Reference in New Issue
Block a user