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 totalRows :: Int page :: Int pageSize :: Int primaryKeyFields :: [Text] tableCols :: [ColumnDefinition] rows :: [[DynamicField]] tableName :: Text tableNames :: [Text] $sel:totalRows:ShowTableRowsView :: ShowTableRowsView -> Int $sel:page:ShowTableRowsView :: ShowTableRowsView -> Int $sel:pageSize:ShowTableRowsView :: ShowTableRowsView -> Int $sel:primaryKeyFields:ShowTableRowsView :: ShowTableRowsView -> [Text] $sel:tableCols:ShowTableRowsView :: ShowTableRowsView -> [ColumnDefinition] $sel:rows:ShowTableRowsView :: ShowTableRowsView -> [[DynamicField]] $sel:tableName:ShowTableRowsView :: ShowTableRowsView -> Text $sel:tableNames:ShowTableRowsView :: ShowTableRowsView -> [Text] .. } = [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-" forall a. Semigroup a => a -> a -> a <> forall a. Show a => a -> Text tshow Text primaryKey primaryKey :: Text primaryKey = Text -> [Text] -> Text intercalate Text "---" forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k). Category cat => cat b c -> cat a b -> cat a c . forall a b. (a -> b) -> [a] -> [b] map (forall a b. ConvertibleStrings a b => a -> b cs forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k). Category cat => cat b c -> cat a b -> cat a c . forall a. a -> Maybe a -> a fromMaybe ByteString "" forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k). Category cat => cat b c -> cat a b -> cat a c . forall model (name :: Symbol) value. (KnownSymbol name, HasField name model value) => Proxy name -> model -> value get forall a. IsLabel "fieldValue" a => a #fieldValue) forall a b. (a -> b) -> a -> b $ forall a. (a -> Bool) -> [a] -> [a] filter ((forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool `elem` [Text] primaryKeyFields) forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k). Category cat => cat b c -> cat a b -> cat a c . forall a b. ConvertibleStrings a b => a -> b cs forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k). Category cat => cat b c -> cat a b -> cat a c . forall model (name :: Symbol) value. (KnownSymbol name, HasField name model value) => Proxy name -> model -> value get forall a. IsLabel "fieldName" a => a #fieldName) [DynamicField] fields renderField :: Text -> DynamicField -> Html renderField Text primaryKey DynamicField { Maybe ByteString ByteString $sel:fieldName:DynamicField :: DynamicField -> ByteString $sel:fieldValue:DynamicField :: DynamicField -> Maybe ByteString fieldName :: ByteString fieldValue :: Maybe ByteString .. } | ByteString fieldName 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>|] | forall {a1} {a2} {a3} {t :: * -> *} {model}. (Eq a1, Eq a2, ConvertibleStrings a3 a2, Foldable t, HasField "columnName" model a2, HasField "columnType" model a1, IsString a1) => a3 -> t model -> Bool isBoolField ByteString fieldName [ColumnDefinition] tableCols Bool -> Bool -> Bool && Bool -> Bool not (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 $sel:fieldName:DynamicField :: ByteString $sel:fieldValue:DynamicField :: Maybe ByteString fieldName :: ByteString fieldValue :: Maybe ByteString .. } renderNormalField :: Text -> DynamicField -> Html renderNormalField Text primaryKey DynamicField { Maybe ByteString ByteString fieldName :: ByteString fieldValue :: Maybe ByteString $sel:fieldName:DynamicField :: DynamicField -> ByteString $sel:fieldValue:DynamicField :: DynamicField -> Maybe 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 forall a. a -> Maybe a Just forall a b. (a -> b) -> a -> b $ forall controller. HasPath controller => controller -> Text pathTo ShowForeignKeyHoverCardAction { Text $sel:tableName:ShowDatabaseAction :: Text tableName :: Text tableName, $sel:id:ShowDatabaseAction :: Text id = Text primaryKey, $sel:columnName:ShowDatabaseAction :: Text columnName = forall a b. ConvertibleStrings a b => a -> b cs ByteString fieldName } else forall a. Maybe a Nothing columnNames :: [ByteString] columnNames = forall a b. (a -> b) -> [a] -> [b] map (forall model (name :: Symbol) value. (KnownSymbol name, HasField name model value) => Proxy name -> model -> value get forall a. IsLabel "fieldName" a => a #fieldName) (forall a. a -> Maybe a -> a fromMaybe [] (forall a. [a] -> Maybe a head [[DynamicField]] rows)) onClick :: Text -> a -> Text -> Text onClick Text tableName a fieldName Text primaryKey = Text "window.location.assign(" forall a. Semigroup a => a -> a -> a <> forall a. Show a => a -> Text tshow (forall controller. HasPath controller => controller -> Text pathTo (Text -> Text -> Text -> DataController ToggleBooleanFieldAction Text tableName (forall a b. ConvertibleStrings a b => a -> b cs a fieldName) Text primaryKey)) forall a. Semigroup a => a -> a -> a <> Text ")" totalPages :: [Int] totalPages = [Int 1..forall a b. (RealFrac a, Integral b) => a -> b ceiling (forall a b. (Integral a, Num b) => a -> b fromIntegral(Int totalRows) forall a. Fractional a => a -> a -> a / forall a b. (Integral a, Num b) => a -> b fromIntegral(Int pageSize))] pageMenu :: Html pageMenu = forall (f :: * -> *). Applicative f => Bool -> f () -> f () when (forall (t :: * -> *) a. Foldable t => t a -> Int length [Int] totalPages 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 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 forall a. Ord a => a -> a -> Bool < 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> |]