diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala index 8f2271e..f655da4 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala @@ -30,6 +30,21 @@ ) ) + inline def `check-circle`(size: Int = defaultSize) = + svg( + cls := s"w-${size} h-${size}", + fill := "none", + stroke := "currentColor", + viewBox := "0 0 24 24", + xmlns := "http://www.w3.org/2000/svg", + path( + strokeLineCap := "round", + strokeLineJoin := "round", + strokeWidth := "2", + d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" + ) + ) + inline def `document-add`(size: Int = defaultSize) = svg( cls := s"w-${size} h-${size}", @@ -45,6 +60,21 @@ ) ) + inline def `external-link`(size: Int = defaultSize) = + svg( + cls := s"w-${size} h-${size}", + fill := "none", + stroke := "currentColor", + viewBox := "0 0 24 24", + xmlns := "http://www.w3.org/2000/svg", + path( + strokeLineCap := "round", + strokeLineJoin := "round", + strokeWidth := "2", + d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" + ) + ) + inline def menu(size: Int = defaultSize) = svg( cls := s"h-${size} w-${size}", diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala index 8f2271e..f655da4 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala @@ -30,6 +30,21 @@ ) ) + inline def `check-circle`(size: Int = defaultSize) = + svg( + cls := s"w-${size} h-${size}", + fill := "none", + stroke := "currentColor", + viewBox := "0 0 24 24", + xmlns := "http://www.w3.org/2000/svg", + path( + strokeLineCap := "round", + strokeLineJoin := "round", + strokeWidth := "2", + d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" + ) + ) + inline def `document-add`(size: Int = defaultSize) = svg( cls := s"w-${size} h-${size}", @@ -45,6 +60,21 @@ ) ) + inline def `external-link`(size: Int = defaultSize) = + svg( + cls := s"w-${size} h-${size}", + fill := "none", + stroke := "currentColor", + viewBox := "0 0 24 24", + xmlns := "http://www.w3.org/2000/svg", + path( + strokeLineCap := "round", + strokeLineJoin := "round", + strokeWidth := "2", + d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" + ) + ) + inline def menu(size: Int = defaultSize) = svg( cls := s"h-${size} w-${size}", diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FilePicker.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FilePicker.scala index 53b62b8..6fdbaa2 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FilePicker.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FilePicker.scala @@ -1,14 +1,13 @@ package cz.e_bs.cmi.mdr.pdb.app.components.files import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec object FilePicker: import cz.e_bs.cmi.mdr.pdb.app.components.files val File = files.File val Selector = FileSelector _ - def apply(content: HtmlElement): HtmlElement = + def apply(display: Signal[List[File]] => HtmlElement): HtmlElement = // This sequence tricks browser into displaying modal content centered // Inspired by modal in headless ui playground // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 @@ -20,6 +19,14 @@ inline def overlay: Modifier[HtmlElement] = // Page overlay + /* TODO: transition + enter="ease-out duration-300" + enterFrom="opacity-0" + enterTo="opacity-100" + leave="ease-in duration-200" + leaveFrom="opacity-100" + leaveTo="opacity-0" + */ div( div( cls("fixed inset-0 transition-opacity"), @@ -27,55 +34,38 @@ ) ) + val selectedFiles = Var[Set[File]](Set.empty) + div( - cls("fixed inset-0 z-10 overflow-y-auto"), div( - cls( - "flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0" - ), - overlay, - browserCenteringModalTrick, + cls("fixed inset-0 z-10 overflow-y-auto"), div( cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" + "text-center sm:block sm:p-0" ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) + overlay, + browserCenteringModalTrick, + Selector( + Val( + List( + File( + "https://tc163.cmi.cz/first_file", + "Smlouva o pracovním poměru" + ), + File( + "https://tc163.cmi.cz/first_file", + "Vysokoškolský diplom" + ), + File( + "https://tc163.cmi.cz/first_file", + "Prezenční listina školení" + ), + File("https://tc163.cmi.cz/first_file", "Životopis") ) ), - Selector( - Val( - List( - File( - "https://tc163.cmi.cz/first_file", - "Smlouva o pracovním poměru" - ), - File( - "https://tc163.cmi.cz/first_file", - "Diplom k vystudování VŠ" - ), - File( - "https://tc163.cmi.cz/first_file", - "Prezenční listina školení" - ), - File("https://tc163.cmi.cz/first_file", "Životopis") - ) - ) - ) + selectedFiles ) ) ), - content + display(selectedFiles.signal.map(_.to(List))) ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala index 8f2271e..f655da4 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala @@ -30,6 +30,21 @@ ) ) + inline def `check-circle`(size: Int = defaultSize) = + svg( + cls := s"w-${size} h-${size}", + fill := "none", + stroke := "currentColor", + viewBox := "0 0 24 24", + xmlns := "http://www.w3.org/2000/svg", + path( + strokeLineCap := "round", + strokeLineJoin := "round", + strokeWidth := "2", + d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" + ) + ) + inline def `document-add`(size: Int = defaultSize) = svg( cls := s"w-${size} h-${size}", @@ -45,6 +60,21 @@ ) ) + inline def `external-link`(size: Int = defaultSize) = + svg( + cls := s"w-${size} h-${size}", + fill := "none", + stroke := "currentColor", + viewBox := "0 0 24 24", + xmlns := "http://www.w3.org/2000/svg", + path( + strokeLineCap := "round", + strokeLineJoin := "round", + strokeWidth := "2", + d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" + ) + ) + inline def menu(size: Int = defaultSize) = svg( cls := s"h-${size} w-${size}", diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FilePicker.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FilePicker.scala index 53b62b8..6fdbaa2 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FilePicker.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FilePicker.scala @@ -1,14 +1,13 @@ package cz.e_bs.cmi.mdr.pdb.app.components.files import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec object FilePicker: import cz.e_bs.cmi.mdr.pdb.app.components.files val File = files.File val Selector = FileSelector _ - def apply(content: HtmlElement): HtmlElement = + def apply(display: Signal[List[File]] => HtmlElement): HtmlElement = // This sequence tricks browser into displaying modal content centered // Inspired by modal in headless ui playground // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 @@ -20,6 +19,14 @@ inline def overlay: Modifier[HtmlElement] = // Page overlay + /* TODO: transition + enter="ease-out duration-300" + enterFrom="opacity-0" + enterTo="opacity-100" + leave="ease-in duration-200" + leaveFrom="opacity-100" + leaveTo="opacity-0" + */ div( div( cls("fixed inset-0 transition-opacity"), @@ -27,55 +34,38 @@ ) ) + val selectedFiles = Var[Set[File]](Set.empty) + div( - cls("fixed inset-0 z-10 overflow-y-auto"), div( - cls( - "flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0" - ), - overlay, - browserCenteringModalTrick, + cls("fixed inset-0 z-10 overflow-y-auto"), div( cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" + "text-center sm:block sm:p-0" ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) + overlay, + browserCenteringModalTrick, + Selector( + Val( + List( + File( + "https://tc163.cmi.cz/first_file", + "Smlouva o pracovním poměru" + ), + File( + "https://tc163.cmi.cz/first_file", + "Vysokoškolský diplom" + ), + File( + "https://tc163.cmi.cz/first_file", + "Prezenční listina školení" + ), + File("https://tc163.cmi.cz/first_file", "Životopis") ) ), - Selector( - Val( - List( - File( - "https://tc163.cmi.cz/first_file", - "Smlouva o pracovním poměru" - ), - File( - "https://tc163.cmi.cz/first_file", - "Diplom k vystudování VŠ" - ), - File( - "https://tc163.cmi.cz/first_file", - "Prezenční listina školení" - ), - File("https://tc163.cmi.cz/first_file", "Životopis") - ) - ) - ) + selectedFiles ) ) ), - content + display(selectedFiles.signal.map(_.to(List))) ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileSelector.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileSelector.scala index fe47005..3822cc1 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileSelector.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileSelector.scala @@ -2,8 +2,14 @@ import com.raquo.laminar.api.L.{*, given} import com.raquo.domtypes.generic.codecs.StringAsIsCodec +import cz.e_bs.cmi.mdr.pdb.app.components.Icons +import io.laminext.syntax.core.{*, given} +import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -def FileTable(files: Signal[List[File]]): HtmlElement = +def FileTable( + files: Signal[List[File]], + selectedFiles: Var[Set[File]] +): HtmlElement = val scope = customHtmlAttr("scope", StringAsIsCodec) def headerRow: HtmlElement = @@ -13,23 +19,50 @@ "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" ) tr( + th(baseM, span(cls("sr-only"), "Vybrat")), th(baseM, textH, "Název"), - th(baseM, textH, "Kategorie"), th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) ) - def tableRow(f: File, idx: Int): HtmlElement = + def tableRow( + f: File, + idx: Int, + selected: Boolean + )(toggleSelection: Observer[Unit]): HtmlElement = val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") tr( cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td(baseC, cls("font-medium text-gray-900"), f.name), - td(baseC, cls("text-gray-500"), "kategorie"), - td(baseC, cls("text-right font-medium"), a(href(f.url), "Otevřít")) + td( + cls("font-medium cursor-pointer"), + onClick.mapTo(()) --> toggleSelection, + cls(if selected then "text-green-900" else "text-gray-200"), + Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), + span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") + ), + td( + baseC, + cls("font-medium text-gray-900"), + f.name, + onClick.mapTo(()) --> toggleSelection + ), + td( + baseC, + cls("text-right font-medium"), + a( + href(f.url), + target("_blank"), + cls( + "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" + ), + Icons.outline.`external-link`(), + "Otevřít" + ) + ) ) div( cls("flex flex-col"), - div(cls("-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8")), + div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), div( cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), div( @@ -41,12 +74,70 @@ headerRow ), tbody( - children <-- files.map(_.zipWithIndex.map(tableRow)) + children <-- files + .map(_.zipWithIndex) + .combineWithFn(selectedFiles)((f, sel) => + f.map((file, idx) => + val active = sel.contains(file) + tableRow(file, idx, active)( + selectedFiles.writer + .contramap(_ => if active then sel - file else sel + file) + ) + ) + ) ) ) ) ) ) -def FileSelector(files: Signal[List[File]]): HtmlElement = - div(FileTable(files)) +def FileSelector( + files: Signal[List[File]], + selectedFiles: Var[Set[File]] +): HtmlElement = + div( + cls( + "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" + ), + role("dialog"), + customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), + aria.labelledBy("modal-headline"), + div( + cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), + div( + cls("sm:flex sm:items-start"), + div( + cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), + h3( + cls("text-lg font-medium leading-6 text-gray-900"), + idAttr("modal-headline"), + "Výběr souborů" + ) + ) + ), + FileTable(files, selectedFiles) + ), + div( + cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), + span( + cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), + button( + typ("button"), + cls( + "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" + ), + "Potvrdit" + ) + ), + span( + cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), + button( + typ("button"), + cls( + "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" + ), + "Zrušit" + ) + ) + ) + ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala index 8f2271e..f655da4 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala @@ -30,6 +30,21 @@ ) ) + inline def `check-circle`(size: Int = defaultSize) = + svg( + cls := s"w-${size} h-${size}", + fill := "none", + stroke := "currentColor", + viewBox := "0 0 24 24", + xmlns := "http://www.w3.org/2000/svg", + path( + strokeLineCap := "round", + strokeLineJoin := "round", + strokeWidth := "2", + d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" + ) + ) + inline def `document-add`(size: Int = defaultSize) = svg( cls := s"w-${size} h-${size}", @@ -45,6 +60,21 @@ ) ) + inline def `external-link`(size: Int = defaultSize) = + svg( + cls := s"w-${size} h-${size}", + fill := "none", + stroke := "currentColor", + viewBox := "0 0 24 24", + xmlns := "http://www.w3.org/2000/svg", + path( + strokeLineCap := "round", + strokeLineJoin := "round", + strokeWidth := "2", + d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" + ) + ) + inline def menu(size: Int = defaultSize) = svg( cls := s"h-${size} w-${size}", diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FilePicker.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FilePicker.scala index 53b62b8..6fdbaa2 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FilePicker.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FilePicker.scala @@ -1,14 +1,13 @@ package cz.e_bs.cmi.mdr.pdb.app.components.files import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec object FilePicker: import cz.e_bs.cmi.mdr.pdb.app.components.files val File = files.File val Selector = FileSelector _ - def apply(content: HtmlElement): HtmlElement = + def apply(display: Signal[List[File]] => HtmlElement): HtmlElement = // This sequence tricks browser into displaying modal content centered // Inspired by modal in headless ui playground // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 @@ -20,6 +19,14 @@ inline def overlay: Modifier[HtmlElement] = // Page overlay + /* TODO: transition + enter="ease-out duration-300" + enterFrom="opacity-0" + enterTo="opacity-100" + leave="ease-in duration-200" + leaveFrom="opacity-100" + leaveTo="opacity-0" + */ div( div( cls("fixed inset-0 transition-opacity"), @@ -27,55 +34,38 @@ ) ) + val selectedFiles = Var[Set[File]](Set.empty) + div( - cls("fixed inset-0 z-10 overflow-y-auto"), div( - cls( - "flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0" - ), - overlay, - browserCenteringModalTrick, + cls("fixed inset-0 z-10 overflow-y-auto"), div( cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" + "text-center sm:block sm:p-0" ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) + overlay, + browserCenteringModalTrick, + Selector( + Val( + List( + File( + "https://tc163.cmi.cz/first_file", + "Smlouva o pracovním poměru" + ), + File( + "https://tc163.cmi.cz/first_file", + "Vysokoškolský diplom" + ), + File( + "https://tc163.cmi.cz/first_file", + "Prezenční listina školení" + ), + File("https://tc163.cmi.cz/first_file", "Životopis") ) ), - Selector( - Val( - List( - File( - "https://tc163.cmi.cz/first_file", - "Smlouva o pracovním poměru" - ), - File( - "https://tc163.cmi.cz/first_file", - "Diplom k vystudování VŠ" - ), - File( - "https://tc163.cmi.cz/first_file", - "Prezenční listina školení" - ), - File("https://tc163.cmi.cz/first_file", "Životopis") - ) - ) - ) + selectedFiles ) ) ), - content + display(selectedFiles.signal.map(_.to(List))) ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileSelector.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileSelector.scala index fe47005..3822cc1 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileSelector.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileSelector.scala @@ -2,8 +2,14 @@ import com.raquo.laminar.api.L.{*, given} import com.raquo.domtypes.generic.codecs.StringAsIsCodec +import cz.e_bs.cmi.mdr.pdb.app.components.Icons +import io.laminext.syntax.core.{*, given} +import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -def FileTable(files: Signal[List[File]]): HtmlElement = +def FileTable( + files: Signal[List[File]], + selectedFiles: Var[Set[File]] +): HtmlElement = val scope = customHtmlAttr("scope", StringAsIsCodec) def headerRow: HtmlElement = @@ -13,23 +19,50 @@ "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" ) tr( + th(baseM, span(cls("sr-only"), "Vybrat")), th(baseM, textH, "Název"), - th(baseM, textH, "Kategorie"), th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) ) - def tableRow(f: File, idx: Int): HtmlElement = + def tableRow( + f: File, + idx: Int, + selected: Boolean + )(toggleSelection: Observer[Unit]): HtmlElement = val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") tr( cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td(baseC, cls("font-medium text-gray-900"), f.name), - td(baseC, cls("text-gray-500"), "kategorie"), - td(baseC, cls("text-right font-medium"), a(href(f.url), "Otevřít")) + td( + cls("font-medium cursor-pointer"), + onClick.mapTo(()) --> toggleSelection, + cls(if selected then "text-green-900" else "text-gray-200"), + Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), + span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") + ), + td( + baseC, + cls("font-medium text-gray-900"), + f.name, + onClick.mapTo(()) --> toggleSelection + ), + td( + baseC, + cls("text-right font-medium"), + a( + href(f.url), + target("_blank"), + cls( + "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" + ), + Icons.outline.`external-link`(), + "Otevřít" + ) + ) ) div( cls("flex flex-col"), - div(cls("-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8")), + div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), div( cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), div( @@ -41,12 +74,70 @@ headerRow ), tbody( - children <-- files.map(_.zipWithIndex.map(tableRow)) + children <-- files + .map(_.zipWithIndex) + .combineWithFn(selectedFiles)((f, sel) => + f.map((file, idx) => + val active = sel.contains(file) + tableRow(file, idx, active)( + selectedFiles.writer + .contramap(_ => if active then sel - file else sel + file) + ) + ) + ) ) ) ) ) ) -def FileSelector(files: Signal[List[File]]): HtmlElement = - div(FileTable(files)) +def FileSelector( + files: Signal[List[File]], + selectedFiles: Var[Set[File]] +): HtmlElement = + div( + cls( + "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" + ), + role("dialog"), + customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), + aria.labelledBy("modal-headline"), + div( + cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), + div( + cls("sm:flex sm:items-start"), + div( + cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), + h3( + cls("text-lg font-medium leading-6 text-gray-900"), + idAttr("modal-headline"), + "Výběr souborů" + ) + ) + ), + FileTable(files, selectedFiles) + ), + div( + cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), + span( + cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), + button( + typ("button"), + cls( + "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" + ), + "Potvrdit" + ) + ), + span( + cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), + button( + typ("button"), + cls( + "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" + ), + "Zrušit" + ) + ) + ) + ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala index d474ed9..7761871 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala @@ -47,16 +47,7 @@ "Dokumenty", FilePicker( files - .FileList( - Val( - List( - files.File( - "Pracovní smlouva", - "http://example.com/123.doc" - ) - ) - ) - ) + .FileList(_) .toHtml .amend(idAttr := "dokumenty", cls("max-w-lg")) )