Required classes for our style of building/running shiny applications (no documentation)
This commit is contained in:
1 parent
7c619d6543
commit
fa9a4dd82e
11 files changed
+3414
No files matched your search
@@ -9,3 +9,10 @@ License: GPL (>= 3)
|
|||||||
Encoding: UTF-8
|
Encoding: UTF-8
|
||||||
Roxygen: list(markdown = TRUE)
|
Roxygen: list(markdown = TRUE)
|
||||||
RoxygenNote: 7.3.3
|
RoxygenNote: 7.3.3
|
||||||
|
Imports:
|
||||||
|
R6,
|
||||||
|
rlang,
|
||||||
|
shiny
|
||||||
|
Suggests:
|
||||||
|
AVSDevR.DBClient,
|
||||||
|
AVSDevR.ShinyDisconnectHandler
|
||||||
@@ -1,2 +1,14 @@
|
|||||||
# Generated by roxygen2: do not edit by hand
|
# Generated by roxygen2: do not edit by hand
|
||||||
|
|
||||||
|
export(AdminLTEApplication)
|
||||||
|
export(FrameApplication)
|
||||||
|
export(SessionLogger)
|
||||||
|
export(SessionLoggerConsole)
|
||||||
|
export(SessionLoggerDB)
|
||||||
|
export(UFApplication)
|
||||||
|
export(UFDashboardApplication)
|
||||||
|
export(UFFrameApplication)
|
||||||
|
import(R6)
|
||||||
|
import(rlang)
|
||||||
|
import(shiny)
|
||||||
|
importFrom(R6,R6Class)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#' @importFrom R6 R6Class
|
||||||
|
#' @import R6
|
||||||
|
#' @import rlang
|
||||||
|
#' @import shiny
|
||||||
|
NULL
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
#' @export
|
||||||
|
AdminLTEApplication <- R6::R6Class(
|
||||||
|
"AdminLTEApplication",
|
||||||
|
inherit = FrameApplication,
|
||||||
|
public = list(
|
||||||
|
withSkin = function(skin) {
|
||||||
|
private$skin <- skin
|
||||||
|
invisible(self)
|
||||||
|
},
|
||||||
|
frameUI = function() {
|
||||||
|
super$ui()
|
||||||
|
},
|
||||||
|
ui = function() {
|
||||||
|
ui <- shiny::tags$body(
|
||||||
|
class = paste0("hold-transition skin-", private$skin),
|
||||||
|
`data-skin` = private$skin,
|
||||||
|
style = "min-height: 611px; padding-top: 20px",
|
||||||
|
shinydashboardPlus:::addDeps(
|
||||||
|
super$ui(),
|
||||||
|
md = FALSE,
|
||||||
|
options = NULL
|
||||||
|
)
|
||||||
|
)
|
||||||
|
shiny:::setLang(ui, private$language)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
private = list(
|
||||||
|
skin = "blue"
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
|
||||||
|
#' @export
|
||||||
|
FrameApplication <- R6::R6Class(
|
||||||
|
"FrameApplication",
|
||||||
|
public = list(
|
||||||
|
initialize = function(appPage = NULL, language = NULL) {
|
||||||
|
if (!is.null(appPage)) {
|
||||||
|
private$appPage <- appPage
|
||||||
|
} else {
|
||||||
|
private$appPage <- modules::use("pages/application.R")
|
||||||
|
}
|
||||||
|
if (!is.null(language)) {
|
||||||
|
private$language <- language
|
||||||
|
}
|
||||||
|
invisible(self)
|
||||||
|
},
|
||||||
|
withSessionLogger = function(sessionLogger) {
|
||||||
|
if (!inherits(sessionLogger, "SessionLogger")) {
|
||||||
|
rlang::abort("sessionLogger is not an instance of SessionLogger class")
|
||||||
|
}
|
||||||
|
private$sessionLogger <- sessionLogger
|
||||||
|
invisible(self)
|
||||||
|
},
|
||||||
|
withBootstrap4Shim = function() {
|
||||||
|
private$withBS4 <- TRUE
|
||||||
|
invisible(self)
|
||||||
|
},
|
||||||
|
getSessionDBClient = function(
|
||||||
|
session, db_config = NULL, allowFailure = FALSE
|
||||||
|
) {
|
||||||
|
# Check DBClient package is installed
|
||||||
|
if (!rlang::is_installed("AVSDevR.DBClient")) {
|
||||||
|
return(NULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check connection configuration is available
|
||||||
|
db_conn <- AVSDevR.DBClient::DBConnection$new()
|
||||||
|
if (is.null(db_config)) {
|
||||||
|
if (!db_conn$loadConfiguration(TRUE)) {
|
||||||
|
return(NULL)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
db_conn$configure(db_config)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Try and connect
|
||||||
|
if (!db_conn$connect()) {
|
||||||
|
if (allowFailure) {
|
||||||
|
rlang::abort(
|
||||||
|
"Failed to connect to database - database not available or not \
|
||||||
|
created"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return(NULL)
|
||||||
|
}
|
||||||
|
db_conn$disconnectOnSessionEnd(session)
|
||||||
|
|
||||||
|
# Create the client instance
|
||||||
|
AVSDevR.DBClient::DBClient$new(db_conn)
|
||||||
|
},
|
||||||
|
ui = function() {
|
||||||
|
if (file.exists("widgets/menu.R")) {
|
||||||
|
# Multi-frame application
|
||||||
|
frame <- do.call(tabItems, private$appPage$tabs("application"))
|
||||||
|
frame_tabs <- sapply(frame$children, function(tab) {
|
||||||
|
tab$attr$`data-tabName`
|
||||||
|
})
|
||||||
|
|
||||||
|
menu <- modules::use("widgets/menu.R")
|
||||||
|
menu_tabs <- unname(sapply(
|
||||||
|
menu$ui, function(ti) {
|
||||||
|
ti$children[[1]]$attr$`data-value`
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
if (!identical(sort(menu_tabs), sort(frame_tabs))) {
|
||||||
|
rlang::abort(
|
||||||
|
"Tab names in widgets/menu.R do not match tab names in \
|
||||||
|
pages/application.R!"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
# Single frame application
|
||||||
|
frame <- shiny::fluidPage(
|
||||||
|
title = private$appPage$title(),
|
||||||
|
private$appPage$ui("application")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ui <- shiny::tagList(
|
||||||
|
shiny::tags$head(
|
||||||
|
shiny::singleton(
|
||||||
|
shinyjs::useShinyjs(debug = getOption("debug.shinyjs", FALSE))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
frame,
|
||||||
|
if (rlang::is_installed("AVSDevR.ShinyDisconnectHandler")) {
|
||||||
|
AVSDevR.ShinyDisconnectHandler::useBannerDisconnectHandler()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (private$withBS4) {
|
||||||
|
# Attach boostrap 4 css shim
|
||||||
|
if (getOption("shiny.minified", TRUE)) {
|
||||||
|
css <- "bootstrap-4-shim.min.css"
|
||||||
|
} else {
|
||||||
|
css <- "bootstrap-4-shim.css"
|
||||||
|
}
|
||||||
|
uiDeps <- htmltools::htmlDependency(
|
||||||
|
"AVSDevR.ShinyApplication",
|
||||||
|
as.character(utils::packageVersion("AVSDevR.ShinyApplication")),
|
||||||
|
c(file = system.file(package = "AVSDevR.ShinyApplication")),
|
||||||
|
stylesheet = css
|
||||||
|
)
|
||||||
|
ui <- htmltools::attachDependencies(ui, uiDeps)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set the language
|
||||||
|
shiny:::setLang(ui, private$language)
|
||||||
|
},
|
||||||
|
server = function(input, output, session, ...) {
|
||||||
|
if (!is.null(private$sessionLogger)) {
|
||||||
|
private$sessionLogger$attachSession(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
shiny::callModule(
|
||||||
|
private$appPage$server,
|
||||||
|
"application",
|
||||||
|
db_client = self$getSessionDBClient(session),
|
||||||
|
...
|
||||||
|
)
|
||||||
|
},
|
||||||
|
app = function() {
|
||||||
|
shiny::shinyApp(self$ui(), self$server)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
private = list(
|
||||||
|
appPage = NULL,
|
||||||
|
language = "en",
|
||||||
|
sessionLogger = NULL,
|
||||||
|
withBS4 = FALSE
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
#' @export
|
||||||
|
SessionLogger <- R6::R6Class(
|
||||||
|
"SessionLogger",
|
||||||
|
public = list(
|
||||||
|
wrap = function(app) {
|
||||||
|
wrapped_server <- function(input, output, session, ...) {
|
||||||
|
self$attachSession(session)
|
||||||
|
app$serverFuncSource()(input, output, session, ...)
|
||||||
|
}
|
||||||
|
|
||||||
|
app$serverFuncSource <- function() {
|
||||||
|
wrapped_server
|
||||||
|
}
|
||||||
|
|
||||||
|
app
|
||||||
|
},
|
||||||
|
attachSession = function(session) {
|
||||||
|
private$onSessionStarted(session)
|
||||||
|
session$onSessionEnded(function() {
|
||||||
|
private$onSessionEnded(session)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
),
|
||||||
|
private = list(
|
||||||
|
getClientIp = function(session) {
|
||||||
|
if ("HTTP_X_REAL_IP" %in% names(session$request)) {
|
||||||
|
if (getOption("debug", FALSE)) {
|
||||||
|
cat("Using client IP from HTTP_X_REAL_IP\n")
|
||||||
|
}
|
||||||
|
client_ip <- session$request$HTTP_X_REAL_IP
|
||||||
|
} else if ("HTTP_X_FORWARDED_FOR" %in% names(session$request)) {
|
||||||
|
if (getOption("debug", FALSE)) {
|
||||||
|
cat("Using client IP from HTTP_X_FORWARDED_FOR\n")
|
||||||
|
}
|
||||||
|
client_ip <- strsplit(session$request$HTTP_X_FORWARDED_FOR, ",")[[1]][[1]]
|
||||||
|
} else if ("REMOTE_ADDR" %in% names(session$request)) {
|
||||||
|
if (getOption("debug", FALSE)) {
|
||||||
|
cat("Using client IP from REMOTE_ADDR\n")
|
||||||
|
}
|
||||||
|
client_ip <- session$request$REMOTE_ADDR
|
||||||
|
} else {
|
||||||
|
if (getOption("debug", FALSE)) {
|
||||||
|
cat("Warning: No client IP found\n")
|
||||||
|
}
|
||||||
|
client_ip <- "x.x.x.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onSessionStarted = function(session) {
|
||||||
|
private$logSessionEvent("session_started", session)
|
||||||
|
},
|
||||||
|
|
||||||
|
onSessionEnded = function(session) {
|
||||||
|
private$logSessionEvent("session_ended", session)
|
||||||
|
},
|
||||||
|
|
||||||
|
logSessionEvent = function(
|
||||||
|
event, session = shiny::getDefaultReactiveDomain()
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
#' @export
|
||||||
|
SessionLoggerConsole <- R6::R6Class(
|
||||||
|
"SessionLoggerConsole",
|
||||||
|
inherit = SessionLogger,
|
||||||
|
public = list(),
|
||||||
|
private = list(
|
||||||
|
logSessionEvent = function(
|
||||||
|
event, session = shiny::getDefaultReactiveDomain()
|
||||||
|
) {
|
||||||
|
event_row <- tibble::tibble(
|
||||||
|
app_name = basename(getwd()),
|
||||||
|
app_pid = Sys.getpid(),
|
||||||
|
client_ip = private$getClientIp(session),
|
||||||
|
user_id = session$userData$user_id,
|
||||||
|
session_token = session$token,
|
||||||
|
!!event := lubridate::now()
|
||||||
|
)
|
||||||
|
|
||||||
|
# nolint start: line_length_linter
|
||||||
|
cat("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n")
|
||||||
|
dplyr::glimpse(event_row)
|
||||||
|
cat("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n")
|
||||||
|
# nolint end
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
#' @export
|
||||||
|
SessionLoggerDB <- R6::R6Class(
|
||||||
|
"SessionLoggerDB",
|
||||||
|
inherit = SessionLogger,
|
||||||
|
public = list(
|
||||||
|
initialize = function(db_client) {
|
||||||
|
if (is.null(conn) || !R6::is.R6(conn) || !inherits(conn, "DBClient")) {
|
||||||
|
stop("DBClient instance required!")
|
||||||
|
}
|
||||||
|
private$db_client <- db_client
|
||||||
|
}
|
||||||
|
),
|
||||||
|
private = list(
|
||||||
|
db_client = NULL,
|
||||||
|
|
||||||
|
logSessionEvent = function(
|
||||||
|
event, session = shiny::getDefaultReactiveDomain()
|
||||||
|
) {
|
||||||
|
db_client$connect()
|
||||||
|
on.exit({
|
||||||
|
db_client$disconnect()
|
||||||
|
})
|
||||||
|
|
||||||
|
event_row <- tibble::tibble(
|
||||||
|
app_name = basename(getwd()),
|
||||||
|
app_pid = Sys.getpid(),
|
||||||
|
client_ip = getClientIp(session),
|
||||||
|
user_id = session$userData$user_id,
|
||||||
|
session_token = session$token,
|
||||||
|
!!event := lubridate::now()
|
||||||
|
)
|
||||||
|
|
||||||
|
private$db_client$upsertRows(
|
||||||
|
"shiny_sessions", event_row, key_name = "session_token"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#' @export
|
||||||
|
UFApplication <- R6::R6Class(
|
||||||
|
"UFApplication",
|
||||||
|
inherit = AdminLTEApplication,
|
||||||
|
public = list(
|
||||||
|
configureUF = function(
|
||||||
|
uri, with_users = TRUE, user_optional = FALSE, app_name = NULL
|
||||||
|
) {
|
||||||
|
private$ufURI <- uri
|
||||||
|
if (is.logical(with_users)) {
|
||||||
|
private$with_users <- with_users
|
||||||
|
}
|
||||||
|
if (is.logical(user_optional)) {
|
||||||
|
private$user_optional <- user_optional
|
||||||
|
}
|
||||||
|
if (!is.null(app_name)) {
|
||||||
|
private$app_name <- app_name
|
||||||
|
}
|
||||||
|
invisible(self)
|
||||||
|
},
|
||||||
|
app = function() {
|
||||||
|
AVSDevR.UserFrosting::ufWrapApplication(
|
||||||
|
super$app(), private$ufURI, private$with_users,
|
||||||
|
private$user_optional, private$app_name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
private = list(
|
||||||
|
ufURI = NULL,
|
||||||
|
with_users = TRUE,
|
||||||
|
user_optional = FALSE,
|
||||||
|
app_name = NULL
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
#' @export
|
||||||
|
UFDashboardApplication <- R6::R6Class(
|
||||||
|
"UFDashboardApplication",
|
||||||
|
inherit = UFApplication,
|
||||||
|
public = list(
|
||||||
|
withSidebarCollapsed = function() {
|
||||||
|
private$sidebar_collapsed <- TRUE
|
||||||
|
invisible(self)
|
||||||
|
},
|
||||||
|
ui = function() {
|
||||||
|
if (file.exists("widgets/help.R")) {
|
||||||
|
private$mHelp <- modules::use("widgets/help.R")
|
||||||
|
}
|
||||||
|
if (file.exists("widgets/title.R")) {
|
||||||
|
private$mDashboardTitle <- modules::use("widgets/title.R")
|
||||||
|
}
|
||||||
|
if (file.exists("widgets/menu.R")) {
|
||||||
|
private$mDashboardMenu <- modules::use("widgets/menu.R")
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a UI
|
||||||
|
ui <- shinydashboard::dashboardPage(
|
||||||
|
header = shinydashboard::dashboardHeader(
|
||||||
|
title = if (!is.null(private$mDashboardTitle)) {
|
||||||
|
private$mDashboardTitle$ui
|
||||||
|
} else {
|
||||||
|
private$appPage$title()
|
||||||
|
},
|
||||||
|
.list = c(
|
||||||
|
# Help links
|
||||||
|
if (!is.null(private$mHelp)) list(private$mHelp$ui("help")),
|
||||||
|
|
||||||
|
# UF stuff
|
||||||
|
if (length(private$with_users) == 0 || private$with_users) {
|
||||||
|
list(AVSDevR.UserFrosting::ufUiTopbar())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
sidebar = shinydashboard::dashboardSidebar(
|
||||||
|
collapsed = !(
|
||||||
|
length(private$sidebar_collapsed) == 0
|
||||||
|
|| private$sidebar_collapsed == FALSE
|
||||||
|
),
|
||||||
|
shiny::div(
|
||||||
|
id = "mainSidebar",
|
||||||
|
|
||||||
|
# UF stuff
|
||||||
|
if (length(private$with_users) == 0 || private$with_users) {
|
||||||
|
AVSDevR.UserFrosting::ufUiSidebar()
|
||||||
|
},
|
||||||
|
|
||||||
|
# Normal sidebar
|
||||||
|
shinydashboard::sidebarMenu(
|
||||||
|
id = "mainMenu",
|
||||||
|
shiny::tags$li(class = "header", "NAVIGATION"),
|
||||||
|
|
||||||
|
if (!is.null(private$mDashboardMenu)) private$mDashboardMenu$ui
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
body = shinydashboard::dashboardBody(self$frameUI()),
|
||||||
|
title = private$appPage$title(),
|
||||||
|
skin = private$skin
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set the language
|
||||||
|
shiny:::setLang(ui, private$language)
|
||||||
|
},
|
||||||
|
server = function(
|
||||||
|
input, output, session, ufApi = NULL, ufUser = NULL, ...
|
||||||
|
) {
|
||||||
|
# Load help menu server (if supplied)
|
||||||
|
if (!is.null(private$mHelp)) {
|
||||||
|
shiny::callModule(private$mHelp$server, "help")
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load UF server (if user support required)
|
||||||
|
if (length(private$with_users) == 0 || private$with_users) {
|
||||||
|
shiny::callModule(AVSDevR.UserFrosting::ufServer, "UF", ufApi)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Launch the frame
|
||||||
|
super$server(
|
||||||
|
input, output, session, ufApi = ufApi, ufUser = ufUser, ...
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
private = list(
|
||||||
|
sidebar_collapsed = FALSE,
|
||||||
|
|
||||||
|
mHelp = NULL,
|
||||||
|
mDashboardTitle = NULL,
|
||||||
|
mDashboardMenu = NULL
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#' @export
|
||||||
|
UFFrameApplication <- R6::R6Class(
|
||||||
|
"UFFrameApplication",
|
||||||
|
inherit = UFApplication,
|
||||||
|
public = list(),
|
||||||
|
private = list()
|
||||||
|
)
|
||||||
Vendored
+2951
File diff suppressed because it is too large.
Load diff
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
.col-1,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12,.col,.col-auto,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm,.col-sm-auto,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-md,.col-md-auto,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg,.col-lg-auto,.col-xl-1,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl,.col-xl-auto{position:relative;width:100%;min-height:1px;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:-ms-flexbox !important;display:flex !important}.d-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}@media(min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-sm-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-wLine truncated
|
||||||
Reference in new issue
Block a user