module IHP.IDE.Data.View.ShowTableRows where import IHP.ViewPrelude import IHP.IDE.ToolServer.Types import IHP.IDE.Data.View.ShowDatabase import IHP.IDE.Data.View.Layout import qualified Data.ByteString.Char8 as ByteString data ShowTableRowsView = ShowTableRowsView { ShowTableRowsView -> [Text] tableNames :: [Text] , ShowTableRowsView -> Text tableName :: Text , ShowTableRowsView -> [[DynamicField]] rows :: [[DynamicField]] , ShowTableRowsView -> [ColumnDefinition] tableCols :: [ColumnDefinition] , ShowTableRowsView -> [Text] primaryKeyFields :: [Text] , ShowTableRowsView -> Int pageSize :: Int , ShowTableRowsView -> Int page :: Int , ShowTableRowsView -> Int totalRows :: Int } instance View ShowTableRowsView where html :: (?context::ControllerContext, ?view::ShowTableRowsView) => ShowTableRowsView -> Html html ShowTableRowsView { Int [[DynamicField]] [Text] [ColumnDefinition] Text $sel:tableNames:ShowTableRowsView :: ShowTableRowsView -> [Text] $sel:tableName:ShowTableRowsView :: ShowTableRowsView -> Text $sel:rows:ShowTableRowsView :: ShowTableRowsView -> [[DynamicField]] $sel:tableCols:ShowTableRowsView :: ShowTableRowsView -> [ColumnDefinition] $sel:primaryKeyFields:ShowTableRowsView :: ShowTableRowsView -> [Text] $sel:pageSize:ShowTableRowsView :: ShowTableRowsView -> Int $sel:page:ShowTableRowsView :: ShowTableRowsView -> Int $sel:totalRows:ShowTableRowsView :: ShowTableRowsView -> Int tableNames :: [Text] tableName :: Text rows :: [[DynamicField]] tableCols :: [ColumnDefinition] primaryKeyFields :: [Text] pageSize :: Int page :: Int totalRows :: Int .. } = [hsx| <div class="h-100"> {headerNav} <div class="h-100 row no-gutters"> {renderTableSelector tableNames tableName} <div class="col" oncontextmenu="showContextMenu('context-menu-data-root')"> <div style="overflow: scroll; max-height: 80vh"> {whenNonEmpty rows $ renderRows rows tableBody tableName} {whenEmpty rows emptyState} </div> {pageMenu} </div> </div> </div> <div class="custom-menu menu-for-column shadow backdrop-blur" id="context-menu-data-root"> <a href={NewRowAction tableName}>Add Row</a> </div> |] where tableBody :: Html tableBody = [hsx|<tbody>{forEach rows renderRow}</tbody>|] renderRow :: [DynamicField] -> Html renderRow [DynamicField] fields = [hsx|<tr oncontextmenu={"showContextMenu('" <> contextMenuId <> "'); event.stopPropagation();"}>{forEach fields (renderField primaryKey)}</tr> <div class="custom-menu menu-for-column shadow backdrop-blur" id={contextMenuId}> <a href={EditRowAction tableName primaryKey}>Edit Row</a> <a href={DeleteEntryAction primaryKey tableName} class="js-delete">Delete Row</a> <div></div> <a href={NewRowAction tableName}>Add Row</a> </div>|] where contextMenuId :: Text contextMenuId = Text "context-menu-column-" Text -> Text -> Text forall a. Semigroup a => a -> a -> a <> Text -> Text forall a. Show a => a -> Text tshow Text primaryKey primaryKey :: Text primaryKey = Text -> [Text] -> Text intercalate Text "---" ([Text] -> Text) -> ([DynamicField] -> [Text]) -> [DynamicField] -> Text forall b c a. (b -> c) -> (a -> b) -> a -> c forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k). Category cat => cat b c -> cat a b -> cat a c . (DynamicField -> Text) -> [DynamicField] -> [Text] forall a b. (a -> b) -> [a] -> [b] map (ByteString -> Text forall a b. ConvertibleStrings a b => a -> b cs (ByteString -> Text) -> (DynamicField -> ByteString) -> DynamicField -> Text forall b c a. (b -> c) -> (a -> b) -> a -> c forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k). Category cat => cat b c -> cat a b -> cat a c . ByteString -> Maybe ByteString -> ByteString forall a. a -> Maybe a -> a fromMaybe ByteString "" (Maybe ByteString -> ByteString) -> (DynamicField -> Maybe ByteString) -> DynamicField -> ByteString forall b c a. (b -> c) -> (a -> b) -> a -> c forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k). Category cat => cat b c -> cat a b -> cat a c . (.fieldValue)) ([DynamicField] -> Text) -> [DynamicField] -> Text forall a b. (a -> b) -> a -> b $ (DynamicField -> Bool) -> [DynamicField] -> [DynamicField] forall a. (a -> Bool) -> [a] -> [a] filter ((Text -> [Text] -> Bool forall a. Eq a => a -> [a] -> Bool forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool `elem` [Text] primaryKeyFields) (Text -> Bool) -> (DynamicField -> Text) -> DynamicField -> Bool forall b c a. (b -> c) -> (a -> b) -> a -> c forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k). Category cat => cat b c -> cat a b -> cat a c . ByteString -> Text forall a b. ConvertibleStrings a b => a -> b cs (ByteString -> Text) -> (DynamicField -> ByteString) -> DynamicField -> Text forall b c a. (b -> c) -> (a -> b) -> a -> c forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k). Category cat => cat b c -> cat a b -> cat a c . (.fieldName)) [DynamicField] fields renderField :: Text -> DynamicField -> Html renderField Text primaryKey DynamicField { Maybe ByteString ByteString fieldValue :: Maybe ByteString fieldName :: ByteString $sel:fieldValue:DynamicField :: DynamicField -> Maybe ByteString $sel:fieldName:DynamicField :: DynamicField -> ByteString .. } | ByteString fieldName ByteString -> ByteString -> Bool forall a. Eq a => a -> a -> Bool == ByteString "id" = [hsx|<td><span data-fieldname={fieldName}><a class="border rounded p-1" href={EditRowValueAction tableName (cs fieldName) primaryKey}>{renderId (sqlValueToText fieldValue)}</a></span></td>|] | ByteString -> [ColumnDefinition] -> Bool forall {t :: * -> *} {a1} {a2} {a3} {r}. (Foldable t, ConvertibleStrings a1 a2, Eq a2, Eq a3, HasField "columnName" r a2, HasField "columnType" r a3, IsString a3) => a1 -> t r -> Bool isBoolField ByteString fieldName [ColumnDefinition] tableCols Bool -> Bool -> Bool && Bool -> Bool not (Maybe ByteString -> Bool forall a. Maybe a -> Bool isNothing Maybe ByteString fieldValue) = [hsx|<td><span data-fieldname={fieldName}><input type="checkbox" onclick={onClick tableName fieldName primaryKey} checked={sqlValueToText fieldValue == "t"} /></span></td>|] | Bool otherwise = Text -> DynamicField -> Html renderNormalField Text primaryKey DynamicField { Maybe ByteString ByteString fieldValue :: Maybe ByteString fieldName :: ByteString $sel:fieldValue:DynamicField :: Maybe ByteString $sel:fieldName:DynamicField :: ByteString .. } renderNormalField :: Text -> DynamicField -> Html renderNormalField Text primaryKey DynamicField { Maybe ByteString ByteString $sel:fieldValue:DynamicField :: DynamicField -> Maybe ByteString $sel:fieldName:DynamicField :: DynamicField -> ByteString fieldValue :: Maybe ByteString fieldName :: ByteString .. } = [hsx| <td data-foreign-key-column={foreignKeyHoverCardUrl}> <span data-fieldname={fieldName}> <a href={EditRowValueAction tableName (cs fieldName) primaryKey}> {sqlValueToText fieldValue} </a> </span> </td> |] where isForeignKeyColumn :: Bool isForeignKeyColumn = ByteString "_id" ByteString -> ByteString -> Bool `ByteString.isSuffixOf` ByteString fieldName foreignKeyHoverCardUrl :: Maybe Text foreignKeyHoverCardUrl = if Bool isForeignKeyColumn then Text -> Maybe Text forall a. a -> Maybe a Just (Text -> Maybe Text) -> Text -> Maybe Text forall a b. (a -> b) -> a -> b $ DataController -> Text forall controller. HasPath controller => controller -> Text pathTo ShowForeignKeyHoverCardAction { Text tableName :: Text $sel:tableName:ShowDatabaseAction :: Text tableName, $sel:id:ShowDatabaseAction :: Text id = Text primaryKey, $sel:columnName:ShowDatabaseAction :: Text columnName = ByteString -> Text forall a b. ConvertibleStrings a b => a -> b cs ByteString fieldName } else Maybe Text forall a. Maybe a Nothing columnNames :: [ByteString] columnNames = (DynamicField -> ByteString) -> [DynamicField] -> [ByteString] forall a b. (a -> b) -> [a] -> [b] map (.fieldName) ([DynamicField] -> Maybe [DynamicField] -> [DynamicField] forall a. a -> Maybe a -> a fromMaybe [] ([[DynamicField]] -> Maybe [DynamicField] forall a. [a] -> Maybe a head [[DynamicField]] rows)) onClick :: Text -> a -> Text -> Text onClick Text tableName a fieldName Text primaryKey = Text "window.location.assign(" Text -> Text -> Text forall a. Semigroup a => a -> a -> a <> Text -> Text forall a. Show a => a -> Text tshow (DataController -> Text forall controller. HasPath controller => controller -> Text pathTo (Text -> Text -> Text -> DataController ToggleBooleanFieldAction Text tableName (a -> Text forall a b. ConvertibleStrings a b => a -> b cs a fieldName) Text primaryKey)) Text -> Text -> Text forall a. Semigroup a => a -> a -> a <> Text ")" totalPages :: [Int] totalPages = [Int 1..Double -> Int forall b. Integral b => Double -> b forall a b. (RealFrac a, Integral b) => a -> b ceiling (Int -> Double forall a b. (Integral a, Num b) => a -> b fromIntegral(Int totalRows) Double -> Double -> Double forall a. Fractional a => a -> a -> a / Int -> Double forall a b. (Integral a, Num b) => a -> b fromIntegral(Int pageSize))] pageMenu :: Html pageMenu = Bool -> Html -> Html forall (f :: * -> *). Applicative f => Bool -> f () -> f () when ([Int] -> Int forall a. [a] -> Int forall (t :: * -> *) a. Foldable t => t a -> Int length [Int] totalPages Int -> Int -> Bool forall a. Ord a => a -> a -> Bool > Int 1) [hsx| <div style="position: absolute; bottom: 0; height: 30px" class="d-flex justify-content-center w-100" oncontextmenu="showContextMenu('context-menu-pagination'); event.stopPropagation();"> {backButton} {forEach (totalPages) renderPageButton} {nextButton} </div> <div class="custom-menu menu-for-column shadow backdrop-blur" id="context-menu-pagination"> <span class="text-muted mx-3">Display Rows</span> <a href={pathTo (ShowTableRowsAction tableName) <> "&page=" <> show page <> "&rows=20"}>20</a> <a href={pathTo (ShowTableRowsAction tableName) <> "&page=" <> show page <> "&rows=50"}>50</a> <a href={pathTo (ShowTableRowsAction tableName) <> "&page=" <> show page <> "&rows=100"}>100</a> </div> |] where backButton :: Html backButton = if Int page Int -> Int -> Bool forall a. Ord a => a -> a -> Bool > Int 1 then [hsx|<a href={pathTo (ShowTableRowsAction tableName) <> "&page=" <> show (page-1) <> "&rows=" <> show pageSize} class="mx-3 text-muted">{"< Back" :: Text}</a>|] else [hsx|<a class="mx-3 text-muted" style="cursor: not-allowed">{"< Back" :: Text}</a>|] nextButton :: Html nextButton = if Int page Int -> Int -> Bool forall a. Ord a => a -> a -> Bool < [Int] -> Int forall a. [a] -> Int forall (t :: * -> *) a. Foldable t => t a -> Int length [Int] totalPages then [hsx|<a href={pathTo (ShowTableRowsAction tableName) <> "&page=" <> show (page+1) <> "&rows=" <> show pageSize} class="mx-3 text-muted">{"Next >" :: Text}</a>|] else [hsx|<a class="mx-3 text-muted" style="cursor: not-allowed">{"Next >" :: Text}</a>|] renderPageButton :: Int -> Html renderPageButton :: Int -> Html renderPageButton Int nr = [hsx|<a href={pathTo (ShowTableRowsAction tableName) <> "&page=" <> show nr <> "&rows=" <> show pageSize} class={classes ["mx-2", (if page==nr then "text-dark font-weight-bold" else "text-muted")]}>{nr}</a>|] emptyState :: Html emptyState :: Html emptyState = [hsx| <div class="d-flex w-100 h-100 justify-content-center mt-5"> <div class="text-center"> <p class="text-muted">This table has no rows yet.</p> <a href={NewRowAction tableName} class="btn btn-secondary">+ Add first {tableNameToModelName tableName}</a> </div> </div> |]