Commit 1cf9249e authored by arturo's avatar arturo

[layout] split view UI enhancement

* #314: Folder View: Split Horizontal / Vertical for folder or code views
* Sidebar UI/UX enhancement
* SASS members addition for left/right handling
* link handling hook interface
parent 9a73057f
Pipeline #1751 failed with stage
......@@ -9998,23 +9998,40 @@ h3 {
}
.right-handed .forest-layout {
border-right: 1px solid #000;
}
.right-handed .forest-layout:hover {
border-right: 1px solid #dee2e6;
border-right: 2px solid #dee2e6;
}
.left-handed .forest-layout {
border-left: 1px solid #000;
}
.left-handed .forest-layout:hover {
border-left: 1px solid #dee2e6;
border-left: 2px solid #dee2e6;
}
.forest-layout-teaser {
.forest-layout-bottom-teaser {
background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, black 45%);
}
.forest-layout-top-teaser {
background: linear-gradient(to top, rgba(0, 0, 0, 0) 0%, black 45%);
}
.main-page__horizontal-tiles {
border-top: 2px solid #dee2e6;
}
.main-page__horizontal-tiles .tile-block:not(:first-child) {
border-left: 2px solid #dee2e6;
}
.main-page__vertical-tiles {
border-left: 2px solid #dee2e6;
}
.main-page__vertical-tiles .tile-block:not(:first-child) {
border-top: 2px solid #dee2e6;
}
.tile-menu__popover {
border-radius: 0.25rem;
background-color: #000;
}
.navbar-dark.bg-primary {
background-color: #111111 !important;
}
......
......@@ -9950,21 +9950,38 @@ h3 {
}
.right-handed .forest-layout {
border-right: 1px solid #fff;
}
.right-handed .forest-layout:hover {
border-right: 1px solid #dee2e6;
border-right: 2px solid #dee2e6;
}
.left-handed .forest-layout {
border-left: 1px solid #fff;
}
.left-handed .forest-layout:hover {
border-left: 1px solid #dee2e6;
border-left: 2px solid #dee2e6;
}
.forest-layout-teaser {
.forest-layout-bottom-teaser {
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, white 45%);
}
.forest-layout-top-teaser {
background: linear-gradient(to top, rgba(255, 255, 255, 0) 0%, white 45%);
}
.main-page__horizontal-tiles {
border-top: 2px solid #dee2e6;
}
.main-page__horizontal-tiles .tile-block:not(:first-child) {
border-left: 2px solid #dee2e6;
}
.main-page__vertical-tiles {
border-left: 2px solid #dee2e6;
}
.main-page__vertical-tiles .tile-block:not(:first-child) {
border-top: 2px solid #dee2e6;
}
.tile-menu__popover {
border-radius: 0.25rem;
background-color: #fff;
}
/*# sourceMappingURL=bootstrap-default.css.map */
......@@ -9706,21 +9706,38 @@ h3 {
}
.right-handed .forest-layout {
border-right: 1px solid #fff;
}
.right-handed .forest-layout:hover {
border-right: 1px solid #dee2e6;
border-right: 2px solid #dee2e6;
}
.left-handed .forest-layout {
border-left: 1px solid #fff;
}
.left-handed .forest-layout:hover {
border-left: 1px solid #dee2e6;
border-left: 2px solid #dee2e6;
}
.forest-layout-teaser {
.forest-layout-bottom-teaser {
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, white 45%);
}
.forest-layout-top-teaser {
background: linear-gradient(to top, rgba(255, 255, 255, 0) 0%, white 45%);
}
.main-page__horizontal-tiles {
border-top: 2px solid #dee2e6;
}
.main-page__horizontal-tiles .tile-block:not(:first-child) {
border-left: 2px solid #dee2e6;
}
.main-page__vertical-tiles {
border-left: 2px solid #dee2e6;
}
.main-page__vertical-tiles .tile-block:not(:first-child) {
border-top: 2px solid #dee2e6;
}
.tile-menu__popover {
border-radius: 0.25rem;
background-color: #fff;
}
/*# sourceMappingURL=bootstrap-greyson.css.map */
......@@ -9938,21 +9938,38 @@ h3 {
}
.right-handed .forest-layout {
border-right: 1px solid #fff;
}
.right-handed .forest-layout:hover {
border-right: 1px solid #dee2e6;
border-right: 2px solid #dee2e6;
}
.left-handed .forest-layout {
border-left: 1px solid #fff;
}
.left-handed .forest-layout:hover {
border-left: 1px solid #dee2e6;
border-left: 2px solid #dee2e6;
}
.forest-layout-teaser {
.forest-layout-bottom-teaser {
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, white 45%);
}
.forest-layout-top-teaser {
background: linear-gradient(to top, rgba(255, 255, 255, 0) 0%, white 45%);
}
.main-page__horizontal-tiles {
border-top: 2px solid #dee2e6;
}
.main-page__horizontal-tiles .tile-block:not(:first-child) {
border-left: 2px solid #dee2e6;
}
.main-page__vertical-tiles {
border-left: 2px solid #dee2e6;
}
.main-page__vertical-tiles .tile-block:not(:first-child) {
border-top: 2px solid #dee2e6;
}
.tile-menu__popover {
border-radius: 0.25rem;
background-color: #fff;
}
/*# sourceMappingURL=bootstrap-herbie.css.map */
......@@ -9955,21 +9955,38 @@ h3 {
}
.right-handed .forest-layout {
border-right: 1px solid #fff;
}
.right-handed .forest-layout:hover {
border-right: 1px solid #dee2e6;
border-right: 2px solid #dee2e6;
}
.left-handed .forest-layout {
border-left: 1px solid #fff;
}
.left-handed .forest-layout:hover {
border-left: 1px solid #dee2e6;
border-left: 2px solid #dee2e6;
}
.forest-layout-teaser {
.forest-layout-bottom-teaser {
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, white 45%);
}
.forest-layout-top-teaser {
background: linear-gradient(to top, rgba(255, 255, 255, 0) 0%, white 45%);
}
.main-page__horizontal-tiles {
border-top: 2px solid #dee2e6;
}
.main-page__horizontal-tiles .tile-block:not(:first-child) {
border-left: 2px solid #dee2e6;
}
.main-page__vertical-tiles {
border-left: 2px solid #dee2e6;
}
.main-page__vertical-tiles .tile-block:not(:first-child) {
border-top: 2px solid #dee2e6;
}
.tile-menu__popover {
border-radius: 0.25rem;
background-color: #fff;
}
/*# sourceMappingURL=bootstrap-monotony.css.map */
......@@ -59,6 +59,10 @@
line-height: 100%;
}
.tile-menu__item .fa {
margin-right: 8px;
}
.context-menu {
position: absolute;
left: 96px;
......@@ -186,13 +190,6 @@
border: 3px solid white;
}
#page-wrapper {
padding-top: 40px;
padding-left: 16px;
padding-right: 16px;
width: 100%;
}
#user-page-header {
border-bottom: 1px solid black;
}
......@@ -392,10 +389,6 @@ li .leaf:hover a.settings {
visibility: visible;
}
.forest-layout-content > .tree {
margin-top: 20px;
}
.tree ul li {
position: relative;
}
......@@ -572,7 +565,6 @@ li .leaf:hover a.settings {
}
.forest-layout {
padding-top: 8px;
z-index: 909;
position: fixed;
height: calc(100vh - 56px);
......@@ -587,30 +579,81 @@ li .leaf:hover a.settings {
display: none;
}
.forest-layout-teaser {
.forest-layout-action {
display: flex;
}
.right-handed .forest-layout-action {
padding-left: 16px;
padding-right: 8px;
}
.left-handed .forest-layout-action {
padding-left: 8px;
padding-right: 16px;
}
.right-handed .forest-layout-action {
flex-direction: row;
}
.right-handed .forest-layout-action__button {
margin-right: 16px;
}
.left-handed .forest-layout-action {
flex-direction: row-reverse;
}
.left-handed .forest-layout-action__button {
margin-left: 16px;
}
.forest-layout-bottom-teaser {
z-index: 1;
pointer-events: none;
position: fixed;
bottom: 0;
height: 24px;
width: calc(16.6666666667% - 1px);
}
.right-handed .forest-layout-bottom-teaser {
left: 0;
}
.left-handed .forest-layout-bottom-teaser {
right: 0;
}
.right-handed .forest-layout-teaser {
.forest-layout-top-teaser {
z-index: 1;
pointer-events: none;
position: sticky;
top: 0;
height: 24px;
width: calc(100% - 1px);
}
.right-handed .forest-layout-top-teaser {
left: 0;
}
.left-handed .forest-layout-teaser {
.left-handed .forest-layout-top-teaser {
right: 0;
}
.left-handed .forest-layout {
.forest-layout-content > ul.tree {
margin-top: 24px;
margin-bottom: 0;
}
.right-handed .forest-layout-content > ul.tree {
padding-left: 16px;
padding-right: 8px;
}
.left-handed .forest-layout-content > ul.tree {
padding-left: 8px;
padding-right: 16px;
}
.right-handed .forest-layout {
padding-left: 16px;
padding-right: 8px;
.forest-layout-content > ul.tree:last-child {
margin-bottom: 24px;
}
.code-editor .editor .code-area {
......@@ -694,8 +737,10 @@ li .leaf:hover a.settings {
}
.code-editor .editor .html {
flex-grow: 2;
margin-left: 25px;
padding-left: 25px;
margin-left: 8px;
margin-right: 8px;
padding-left: 8px;
padding-right: 8px;
}
.code-editor .editor .html.language-haskell {
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
......@@ -715,6 +760,12 @@ li .leaf:hover a.settings {
.code-editor .editor .html.language-md ol li {
list-style: decimal !important;
}
.code-editor__toolbar {
margin-bottom: 20px;
}
.code-editor__toolbar__type {
width: 200px;
}
.cache-toggle {
cursor: pointer;
......@@ -848,6 +899,79 @@ ul li {
flex-direction: row;
}
.main-page {
flex-grow: 1;
}
.main-page__main-row {
display: flex;
}
.main-page__main-row--with-y-tiles .main-page__main-route {
width: 60%;
}
.main-page__main-row--with-y-tiles .main-page__vertical-tiles {
width: 40%;
}
.main-page__main-row--only-y-tiles {
min-height: calc( 100vh - 56px);
}
.main-page__main-route {
padding: 24px 32px;
width: 100%;
}
.main-page__vertical-tiles {
display: flex;
flex-direction: column;
}
.main-page__horizontal-tiles {
display: flex;
flex-direction: row;
}
.main-page__horizontal-tiles--1 .tile-block {
width: calc( 100% / 1);
}
.main-page__horizontal-tiles--2 .tile-block {
width: calc( 100% / 2);
}
.main-page__horizontal-tiles--3 .tile-block {
width: calc( 100% / 3);
}
.main-page__horizontal-tiles--4 .tile-block {
width: calc( 100% / 4);
}
.main-page__horizontal-tiles--5 .tile-block {
width: calc( 100% / 5);
}
.main-page__horizontal-tiles--6 .tile-block {
width: calc( 100% / 6);
}
.main-page__horizontal-tiles--7 .tile-block {
width: calc( 100% / 7);
}
.main-page__horizontal-tiles--8 .tile-block {
width: calc( 100% / 8);
}
.main-page__horizontal-tiles--9 .tile-block {
width: calc( 100% / 9);
}
.main-page__horizontal-tiles--10 .tile-block {
width: calc( 100% / 10);
}
.tile-block__header {
display: flex;
}
.right-handed .tile-block__header {
justify-content: flex-end;
}
.left-handed .tile-block__header {
justify-content: flex-start;
}
.tile-block__body {
padding: 4px 16px 12px;
}
.range {
width: 400px;
/* some space for the right knob */
......
{"version":3,"sourceRoot":"","sources":["../../src/sass/_menu.sass","../../src/sass/_context_menu.sass","../../src/sass/_graph.sass","../../src/sass/_login.sass","../../src/sass/_tree.sass","../../src/sass/_code_editor.sass","../../src/sass/_styles.sass","../../src/sass/_range_slider.sass","../../src/sass/_annotation.sass","../../src/sass/_folder_view.sass"],"names":[],"mappings":"AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;EAEA;;;AAEF;EACI;EACA;;;AAEJ;EACI;EACA;EACA;EACA;;;AAEJ;EACE;;;AAEF;AACI;EACA;;;AAEJ;AACI;EACA;;;AAGJ;AACA;EACI;;;AAEJ;EACI;EACA;EACA;EACA;;;AAEJ;EACE;EACA;;;AAEF;EACE;;;ACrDF;EACE;EACA;EACA;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;;;AClBF;EACE;EACA;EACA;;;AAEF;AAkCE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAxCA;EAZA;EACA;EAEA;EAWE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEF;EACE;EACA;;AAGA;EACE;EACA;;AACN;EACE;;AACF;EACE;;AAEF;EApCA;EACA;EAEA;EAmCE;EACA;;AACF;EACE;;AACF;EACE;;AAWF;EAEE;EAEA;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;;AAEJ;EACE;;AAEA;EACE;;AAEJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;ACjFJ;EACE;;AACA;EACE;EACA;;;AAEJ;EACE;EACA;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAGF;EACE;;AAEE;EACE;EACA;;AACA;EACE;;;AAIJ;EACE;EACA;EACA;EACA;;;AAKJ;EACE;EACA;EACA;;;AAGJ;EACE;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;;AACF;EACE;EACA;;AACA;EACE;EACA;EACA;EACA;;AACA;EACE;;AACF;EACE;EACA;EACA;EACA;;AACA;EACE;;AACN;EACE;EACA;EACA;EACA;;;AAGN;EACE;EACA;EACA;EACA;EACA;;AAGE;EACE;;;AAEN;EACE;EACA;EACA;EACA;EACA;;AAGE;EACE;;;AAEN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAEJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;AACA;EACE;EACA;;;AAGF;EACE;;;AAEJ;EACI;EACA;;;AAGF;EACE;;;AAEJ;EACE;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;EACA;;;AAEF;EACE;;;ACvKF;EACE;;;AAGA;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGA;EACE;;AACF;EACE;;AAEJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAGN;EACE;;;AAIA;EACE;;AACA;EACE;EACA;EACA;EACA;;AACF;EACE;EACA;EACA;EACA;EACA;;AACF;EACE;;AACF;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;;AACF;EACE;;AAGN;EACE;;AACF;EACE;;AACA;EACE;EACA;;AAEE;EACE;EACA;;AACF;EACE;EACA;;AAIR;EACE;;AACF;EACE;;AACA;EACE;EACA;;AAEE;EACE;EACA;;AACF;EACE;EACA;;AACF;EACE;EACA;;AAEV;EACE;;AACF;EACE;;AAEE;EACE;;AACF;EACE;;AACN;EACE;;AAEE;EACE;;;AAGR;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;;AAEF;EACE;;;AAEN;EAWE;;AAGE;EACE;;AAEA;EACE;;;AAUR;EAEE;EACA,MAFW;EAGX;;;AAEF;EAEE;EACA,OAFW;EAGX;;;AAGA;EACE;;AACF;EACE;;AACF;EACE;;;AAMF;EACE;;;AAEJ;EAEE;EACA;;;AAEF;EAEE;EACA;;;AAEF;EAGE;EACA;EAKA;EACA;EACA;EACA;EAGA;EACA;EACA;EAKA;;AAHA;EACE;;;AASJ;EAKE;EACA;EACA;EACA,QAPS;EAQT;;;AAGF;EACE;;;AAEF;EACE;;;AAEF;EACI;EACA;;;AACJ;EACI;EACA;;;ACxPA;EACE;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EApCR;EACA;EACA;EACA;EACA;EACA;EACA;EAlBA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AA0CM;EACE;EACA;EACA;EACA;EACA;EA5CR;EACA;EACA;EACA;EACA;EACA;EACA;EAlBA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAkDE;EACE;EACA;EACA;EACA;EACA;EACA;;AACF;EACE;EACA;EACA;;AACA;EACE;EACA;;AACF;EACE;EACA;;AACF;EACE;EACA;;AAGE;EACE;;AAEF;EACE;;;ACtFZ;EACE;;;AAEA;EAEE;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;;AAGE;EACE;EACA;;AAEF;EACE;EACA;;;AAER;EAEE;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;;AAGE;EACE;EACA;;AAEF;EACE;EACA;;;AAEV;EACE;;AACA;EACE;;AACF;EACE;EACA;EACA;;;AAIA;EACE;;AACA;EACE;EACA;;AACF;EACE;;AACA;EACE;;AACJ;EACE;;;AAER;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;AAGI;EACE;;AACF;EACE;;;AAEN;EACE;EACA;EACA;;;AAIA;EACE;;AACF;EACE;;;AAEJ;EACE;;AACA;EACE;;;AAEJ;EACE;EACA;;;AAGF;EACE;;;AAEF;EACE;;AAEA;EACE;;AAEF;EACE;;;ACnHJ;EACE;AACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EAEA;EAEA;;AAEA;EACE;EAEA;EACA;EACA;;;AAGN;EACE;;;ACxBJ;EACE;;AAEA;EANE;EACA;;AAQF;EAbE;EACA;;AAeF;EAhBE;EACA;;AAkBF;EAnBE;EACA;;AAqBF;EA1BE;EACA,kBANyB;;AAkC3B;EA7BE;EACA,kBAPqB;;AAsCvB;EAhCE;EACA,kBAJoB;;;AAuCtB;EApCE;EACA,kBANyB;;AA4C3B;EAvCE;EACA,kBAPqB;;AAgDvB;EA1CE;EACA,kBAJoB;;;ACRxB;EACE;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;;;AAEF;EACE;EACA","file":"sass.css"}
\ No newline at end of file
{"version":3,"sourceRoot":"","sources":["../../src/sass/_menu.sass","../../src/sass/_context_menu.sass","../../src/sass/_graph.sass","../../src/sass/_login.sass","../../src/sass/_tree.sass","../../src/sass/abstract/_members.scss","../../src/sass/_code_editor.sass","../../src/sass/_styles.sass","../../src/sass/_range_slider.sass","../../src/sass/_annotation.sass","../../src/sass/_folder_view.sass"],"names":[],"mappings":"AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;EAEA;;;AAEF;EACI;EACA;;;AAEJ;EACI;EACA;EACA;EACA;;;AAEJ;EACE;;;AAEF;AACI;EACA;;;AAEJ;AACI;EACA;;;AAGJ;AACA;EACI;;;AAEJ;EACI;EACA;EACA;EACA;;;AAEJ;EACE;EACA;;;AAEF;EACE;;;AAME;EACE;;;AC9DN;EACE;EACA;EACA;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;;;AClBF;EACE;EACA;EACA;;;AAEF;AAkCE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAxCA;EAZA;EACA;EAEA;EAWE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEF;EACE;EACA;;AAGA;EACE;EACA;;AACN;EACE;;AACF;EACE;;AAEF;EApCA;EACA;EAEA;EAmCE;EACA;;AACF;EACE;;AACF;EACE;;AAWF;EAEE;EAEA;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;;AAEJ;EACE;;AAEA;EACE;;AAEJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;ACjFJ;EACE;;AACA;EACE;EACA;;;AAEJ;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAGF;EACE;;AAEE;EACE;EACA;;AACA;EACE;;;AAIJ;EACE;EACA;EACA;EACA;;;AAKJ;EACE;EACA;EACA;;;AAGJ;EACE;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;;AACF;EACE;EACA;;AACA;EACE;EACA;EACA;EACA;;AACA;EACE;;AACF;EACE;EACA;EACA;EACA;;AACA;EACE;;AACN;EACE;EACA;EACA;EACA;;;AAGN;EACE;EACA;EACA;EACA;EACA;;AAGE;EACE;;;AAEN;EACE;EACA;EACA;EACA;EACA;;AAGE;EACE;;;AAEN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAEJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;AACA;EACE;EACA;;;AAGF;EACE;;;AAEJ;EACI;EACA;;;AAGF;EACE;;;AAEJ;EACE;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;EACA;;;AAEF;EACE;;;AC/IF;EACE;;;AAGA;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGA;EACE;;AACF;EACE;;AAEJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAKJ;EACE;;AACA;EACE;EACA;EACA;EACA;;AACF;EACE;EACA;EACA;EACA;EACA;;AACF;EACE;;AACF;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;;AACF;EACE;;AAGN;EACE;;AACF;EACE;;AACA;EACE;EACA;;AAEE;EACE;EACA;;AACF;EACE;EACA;;AAIR;EACE;;AACF;EACE;;AACA;EACE;EACA;;AAEE;EACE;EACA;;AACF;EACE;EACA;;AACF;EACE;EACA;;AAEV;EACE;;AACF;EACE;;AAEE;EACE;;AACF;EACE;;AACN;EACE;;AAEE;EACE;;;AAGR;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;;AAEF;EACE;;;AAEN;EAWE;;AAGE;EACE;;AAEA;EACE;;;AAUR;EAEE;EACA,MAFW;EAGX;;;AAEF;EAEE;EACA,OAFW;EAGX;;;AAGA;EACE;;AACF;EACE;;AACF;EACE;;;AAMF;EACE;;;AAEJ;EAEE;EACA;;;AAEF;EAEE;EACA;;;AAEF;EAGE;EAKA;EACA;EACA;EACA;EAGA;EACA;EACA;EAKA;;AAHA;EACE;;;AAKJ;EAGE;;AC9NS;ED/BP;EACA;;;ACqBO;EDlBP;EACA;;;AC0BO;EDiOP;;AAEA;EACE;;;AC7OK;EDgPP;;AAEA;EACE;;;AAON;EAIE;EAEA;EACA;EACA;EACA,QA9RmC;EA+RnC;;AC3PS;ED+PP;;;ACxQO;ED2QP;;;AAEJ;EAGE;EAEA;EACA;EACA;EACA,QAjTgC;EAkThC;;AC7QS;EDiRP;;;AC1RO;ED6RP;;;AAKF;EAIE;EACA;;AC9RO;ED/BP;EACA;;;ACqBO;EDlBP;EACA;;;AA0TA;EACE,eArU+B;;;AEsBjC;EACE;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EApCR;EACA;EACA;EACA;EACA;EACA;EACA;EAlBA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AA0CM;EACE;EACA;EACA;EACA;EACA;EA5CR;EACA;EACA;EACA;EACA;EACA;EACA;EAlBA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAkDE;EACE;EACA;EACA;EACA;EACA;EACA;;AACF;EACE;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;;AACF;EACE;EACA;;AACF;EACE;EACA;;AAGE;EACE;;AAEF;EACE;;AAEV;EACE;;AAEA;EACE;;;AC9FN;EACE;;;AAEA;EAEE;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;;AAGE;EACE;EACA;;AAEF;EACE;EACA;;;AAER;EAEE;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;;AAGE;EACE;EACA;;AAEF;EACE;EACA;;;AAEV;EACE;;AACA;EACE;;AACF;EACE;EACA;EACA;;;AAIA;EACE;;AACA;EACE;EACA;;AACF;EACE;;AACA;EACE;;AACJ;EACE;;;AAER;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;AAGI;EACE;;AACF;EACE;;;AAEN;EACE;EACA;EACA;;;AAIA;EACE;;AACF;EACE;;;AAEJ;EACE;;AACA;EACE;;;AAEJ;EACE;EACA;;;AAGF;EACE;;;AAEF;EACE;;AAEA;EACE;;AAEF;EACE;;;AAGJ;EAKE;;AAEA;EACE;;AAIE;EACE;;AACF;EACE;;AAEJ;EAGE;;AAGJ;EACE,SAtBa;EAuBb;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAKE;EACE;;AADF;EACE;;AADF;EACE;;AADF;EACE;;AADF;EACE;;AADF;EACE;;AADF;EACE;;AADF;EACE;;AADF;EACE;;AADF;EACE;;;AAKN;EACE;;AFhIO;EEmIL;;;AF5IK;EE+IL;;;AAEJ;EACE,SAZa;;;ACpKjB;EACE;AACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EAEA;EAEA;;AAEA;EACE;EAEA;EACA;EACA;;;AAGN;EACE;;;ACxBJ;EACE;;AAEA;EANE;EACA;;AAQF;EAbE;EACA;;AAeF;EAhBE;EACA;;AAkBF;EAnBE;EACA;;AAqBF;EA1BE;EACA,kBANyB;;AAkC3B;EA7BE;EACA,kBAPqB;;AAsCvB;EAhCE;EACA,kBAJoB;;;AAuCtB;EApCE;EACA,kBANyB;;AA4C3B;EAvCE;EACA,kBAPqB;;AAgDvB;EA1CE;EACA,kBAJoB;;;ACNxB;EACE;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;;;AAEF;EACE;EACA","file":"sass.css"}
\ No newline at end of file
......@@ -88,6 +88,7 @@ to generate this file without the comments in this block.
, "unfoldable"
, "unsafe-coerce"
, "uri"
, "uuid"
, "web-file"
, "web-html"
, "web-storage"
......
......@@ -11,7 +11,7 @@ import Gargantext.Components.GraphExplorer.Sidebar.Types as GEST
import Gargantext.Components.Nodes.Lists.Types as ListsT
import Gargantext.Components.Nodes.Texts.Types as TextsT
import Gargantext.Ends (Backend)
import Gargantext.Routes (AppRoute(Home))
import Gargantext.Routes (AppRoute(Home), Tile)
import Gargantext.Sessions (Session, Sessions)
import Gargantext.Sessions as Sessions
import Gargantext.Sessions.Types (OpenNodes(..))
......@@ -19,68 +19,74 @@ import Gargantext.Types (FrontendError, Handed(RightHanded), SidePanelState(..))
import Gargantext.Utils.Toestand as T2
type App =
{ backend :: Maybe Backend
, errors :: Array FrontendError
, forestOpen :: OpenNodes
, graphVersion :: T2.Reload
, handed :: Handed
, reloadForest :: T2.Reload
, reloadMainPage :: T2.Reload
, reloadRoot :: T2.Reload
, route :: AppRoute
, session :: Maybe Session
, sessions :: Sessions
, showCorpus :: Boolean
, showLogin :: Boolean
, showTree :: Boolean
, sidePanelGraph :: Maybe (Record GEST.SidePanel)
, sidePanelLists :: Maybe (Record ListsT.SidePanel)
, sidePanelTexts :: Maybe (Record TextsT.SidePanel)
, sidePanelState :: SidePanelState
, tasks :: GAT.Storage
{ backend :: Maybe Backend
, errors :: Array FrontendError
, forestOpen :: OpenNodes
, graphVersion :: T2.Reload
, handed :: Handed
, reloadForest :: T2.Reload
, reloadMainPage :: T2.Reload
, reloadRoot :: T2.Reload
, route :: AppRoute
, session :: Maybe Session
, sessions :: Sessions
, showCorpus :: Boolean
, showLogin :: Boolean
, showTree :: Boolean
, sidePanelGraph :: Maybe (Record GEST.SidePanel)
, sidePanelLists :: Maybe (Record ListsT.SidePanel)
, sidePanelTexts :: Maybe (Record TextsT.SidePanel)
, sidePanelState :: SidePanelState
, tasks :: GAT.Storage
, tileAxisXList :: Array (Record Tile)
, tileAxisYList :: Array (Record Tile)
}
emptyApp :: App
emptyApp =
{ backend : Nothing
, errors : []
, forestOpen : OpenNodes $ Set.empty
, graphVersion : T2.newReload
, handed : RightHanded
, reloadForest : T2.newReload
, reloadMainPage : T2.newReload
, reloadRoot : T2.newReload
, route : Home
, session : Nothing
, sessions : Sessions.empty
, showCorpus : false
, showLogin : false
, showTree : true
, sidePanelGraph : GEST.initialSidePanel
, sidePanelLists : ListsT.initialSidePanel
, sidePanelTexts : TextsT.initialSidePanel
, sidePanelState : InitialClosed
, tasks : GAT.empty
{ backend : Nothing
, errors : []
, forestOpen : OpenNodes $ Set.empty
, graphVersion : T2.newReload
, handed : RightHanded
, reloadForest : T2.newReload
, reloadMainPage : T2.newReload
, reloadRoot : T2.newReload
, route : Home
, session : Nothing
, sessions : Sessions.empty
, showCorpus : false
, showLogin : false
, showTree : true
, sidePanelGraph : GEST.initialSidePanel
, sidePanelLists : ListsT.initialSidePanel
, sidePanelTexts : TextsT.initialSidePanel
, sidePanelState : InitialClosed
, tasks : GAT.empty
, tileAxisXList : mempty
, tileAxisYList : mempty
}
type Boxes =
{ backend :: T.Box (Maybe Backend)
, errors :: T.Box (Array FrontendError)
, forestOpen :: T.Box OpenNodes
, graphVersion :: T2.ReloadS
, handed :: T.Box Handed
, reloadForest :: T2.ReloadS
, reloadMainPage :: T2.ReloadS
, reloadRoot :: T2.ReloadS
, route :: T.Box AppRoute
, session :: T.Box (Maybe Session)
, sessions :: T.Box Sessions
, showCorpus :: T.Box Boolean
, showLogin :: T.Box Boolean
, showTree :: T.Box Boolean
, sidePanelGraph :: T.Box (Maybe (Record GEST.SidePanel))
, sidePanelLists :: T.Box (Maybe (Record ListsT.SidePanel))
, sidePanelTexts :: T.Box (Maybe (Record TextsT.SidePanel))
, sidePanelState :: T.Box SidePanelState
, tasks :: T.Box GAT.Storage
{ backend :: T.Box (Maybe Backend)
, errors :: T.Box (Array FrontendError)
, forestOpen :: T.Box OpenNodes
, graphVersion :: T2.ReloadS
, handed :: T.Box Handed
, reloadForest :: T2.ReloadS
, reloadMainPage :: T2.ReloadS
, reloadRoot :: T2.ReloadS
, route :: T.Box AppRoute
, session :: T.Box (Maybe Session)
, sessions :: T.Box Sessions
, showCorpus :: T.Box Boolean
, showLogin :: T.Box Boolean
, showTree :: T.Box Boolean
, sidePanelGraph :: T.Box (Maybe (Record GEST.SidePanel))
, sidePanelLists :: T.Box (Maybe (Record ListsT.SidePanel))
, sidePanelTexts :: T.Box (Maybe (Record TextsT.SidePanel))
, sidePanelState :: T.Box SidePanelState
, tasks :: T.Box GAT.Storage
, tileAxisXList :: T.Box (Array (Record Tile))
, tileAxisYList :: T.Box (Array (Record Tile))
}
......@@ -123,9 +123,9 @@ codeEditorCpt = here.component "codeEditor" cpt
pure $ H.div { className: "code-editor" }
[ toolbar { controls, onChange }
, H.div { className: "row error" }
, H.div { className: "row no-gutters error" }
[ errorComponent {error: controls.error} ]
, H.div { className: "row editor" }
, H.div { className: "row no-gutters editor" }
[ H.div { className: "code-area " <> (codeHidden viewType') }
[ H.div { className: "code-container" }
[ H.textarea { defaultValue: codeS'
......@@ -217,15 +217,15 @@ toolbarCpt = here.component "toolbar" cpt
codeType' <- T.useLive T.unequal codeType
pure $
H.div { className: "row toolbar" }
[ H.div { className: "col-2" }
H.div { className: "row no-gutters align-items-center mb-3 code-editor__toolbar" }
[ H.div { className: "code-editor__toolbar__type" }
[ codeTypeSelector {
codeType
-- Handle rerendering of preview when viewType changed
, onChange: \ct -> onChange ct codeS'
}
]
, H.div { className: "col-1" }
, H.div {}
[ viewTypeSelector {state: viewType} [] ]
]
......@@ -246,7 +246,7 @@ errorComponentCpt = here.component "errorComponent" cpt
pure $ case error' of
Nothing -> H.div {} []
Just err -> H.div { className: "text-danger" } [ H.text err ]
Just err -> H.div { className: "text-danger mb-3" } [ H.text err ]
type CodeTypeSelectorProps =
......
......@@ -26,7 +26,7 @@ errorsViewCpt = here.component "errorsView" cpt
where
cpt { errors } _ = do
errors' <- T.useLive T.unequal errors
pure $ H.div {}
( mapWithIndex (showError errors) errors' )
showError errors i (FStringError { error }) =
......
......@@ -76,7 +76,7 @@ folderViewCpt = here.component "folderViewCpt" cpt where
where
errorHandler err = here.log2 "[folderView] RESTError" err
type FolderViewProps =
type FolderViewProps =
( backFolder :: Boolean
, boxes :: Boxes
, folders :: FTree
......@@ -140,7 +140,7 @@ folderViewMainCpt = here.component "folderViewMainCpt" cpt where
isBackHome _ = false
type FolderSimpleProps =
type FolderSimpleProps =
(
style :: FolderStyle
, text :: String
......@@ -161,7 +161,7 @@ folderSimpleCpt = here.component "folderSimpleCpt" cpt where
[ H.i { className: icon style nodeType } []
, H.br {}
, H.text text ]
icon :: FolderStyle -> GT.NodeType -> String
icon FolderUp _ = "fa fa-folder-open"
icon _ nodeType = GT.fldr nodeType false
......@@ -169,7 +169,7 @@ folderSimpleCpt = here.component "folderSimpleCpt" cpt where
getFolderPath :: GT.NodeType -> GT.SessionId -> Int -> String
getFolderPath nodeType sid nodeId = appPath $ fromMaybe Home $ nodeTypeAppRoute nodeType sid nodeId
type FolderProps =
type FolderProps =
( boxes :: Boxes
, parentId :: Int
, reload :: T.Box T2.Reload
......@@ -197,24 +197,24 @@ folderCpt = here.component "folderCpt" cpt where
R.useEffect' $ do
R.setRef setPopoverRef $ Just $ Popover.setOpen popoverRef
pure $
pure $
H.div {} [
H.span{style: {position: "absolute"}} [ Popover.popover {
arrow: false
, open: false
, onClose: \_ -> pure unit
, onOpen: \_ -> pure unit
, ref: popoverRef
, ref: popoverRef
} [
popOverIcon
, mNodePopupView (Record.merge props { dispatch }) (onPopoverClose popoverRef)
]]
, H.button {on: {click: link ("/#/" <> getFolderPath nodeType sid nodeId) }, className: "btn btn-primary fv btn" } [
, H.button {on: {click: link ("/#/" <> getFolderPath nodeType sid nodeId) }, className: "btn btn-primary fv btn" } [
H.i {className: icon style nodeType} []
, H.br {}
, H.text text]]
icon :: FolderStyle -> GT.NodeType -> String
icon FolderUp _ = "fa fa-folder-open"
icon _ nodeType = GT.fldr nodeType false
......@@ -225,7 +225,7 @@ folderCpt = here.component "folderCpt" cpt where
onPopoverClose popoverRef _ = Popover.setOpen popoverRef false
popOverIcon = H.span { className: "fv action" } [
H.a { className: "settings fa fa-cog"
H.a { className: "settings fa fa-cog"
, title : "Each node of the Tree can perform some actions.\n"
<> "Click here to execute one of them." } []
]
......@@ -240,7 +240,7 @@ folderCpt = here.component "folderCpt" cpt where
}
backButton :: R.Element
backButton =
backButton =
H.button {
className: "btn btn-primary"
, on: {click: back}
......@@ -248,15 +248,6 @@ backButton =
H.i { className: "fa fa-arrow-left", title: "Previous view"} []
]
homeButton :: R.Element
homeButton =
H.a {
className: "btn btn-primary"
, href: appPath Home
} [
H.i { className: "fa fa-home", title: "Back to home"} []
]
type LoadProps =
(
session :: Session,
......@@ -299,7 +290,7 @@ performAction = performAction' where
closePopover { setPopoverRef } =
liftEffect $ traverse_ (\set -> set false) (R.readRef setPopoverRef)
refreshFolders p@{ boxes: { reloadForest }, reload } = do
refreshFolders p@{ boxes: { reloadForest }, reload } = do
liftEffect $ T2.reload reload
liftEffect $ T2.reload reloadForest
closePopover p
......@@ -320,7 +311,7 @@ performAction = performAction' where
handleRESTError errors eTask $ \task -> liftEffect $ do
GAT.insert id task tasks
here.log2 "[performAction] UpdateNode task:" task
shareTeam username { boxes: { errors }, nodeId: id, session } = do
eTask <- Share.shareReq session id $ Share.ShareTeamParams { username }
handleRESTError errors eTask $ \_task -> pure unit
......
......@@ -7,12 +7,13 @@ module Gargantext.Components.Forest
import Gargantext.Prelude
import Data.Array as A
import Data.Foldable (intercalate)
import Data.Maybe (Maybe(..))
import Gargantext.Components.App.Data (Boxes)
import Gargantext.Components.Forest.Tree (treeLoader)
import Gargantext.Ends (Frontends)
import Gargantext.Routes (AppRoute(..), appPath)
import Gargantext.Sessions (Session(..), unSessions)
import Gargantext.Types (switchHanded)
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
......@@ -51,7 +52,7 @@ forestCpt = here.component "forest" cpt where
(A.cons (plus { boxes }) (trees handed' sessions'))
where
trees handed' sessions' = (tree handed') <$> unSessions sessions'
tree handed' s@(Session { treeId }) =
tree handed' s@(Session { treeId }) =
treeLoader { boxes
, frontends
, handed: handed'
......@@ -65,16 +66,41 @@ plus :: R2.Leaf Plus
plus p = R.createElement plusCpt p []
plusCpt :: R.Component Plus
plusCpt = here.component "plus" cpt where
cpt { boxes: { backend, handed, showLogin } } _ = do
handed' <- T.useLive T.unequal handed
cpt { boxes: { backend, showLogin } } _ = pure $
pure $ H.div {}
[ H.button { className: buttonClass handed'
, on: { click }
, title }
[ H.div { className: divClass } [ H.text " Log in/out " ] -- fa-lg
, H.div {} [ H.text " " ] ]
H.div
{ className: "forest-layout-action" }
[
H.a
{ className: intercalate " "
[ "btn btn-primary"
, "forest-layout-action__button"
]
, href: appPath Home
}
[
H.i
{ className: "fa fa-home"
, title: "Back to home"
}
[]
]
,
H.button
{ className: intercalate " "
[ "btn btn-primary d-block"
, "forest-layout-action__button"
]
, on: { click }
, title: "Add or remove connections to the server(s)."
}
[
H.span
{ className: "fa fa-universal-access" }
[ H.text " Log in/out " ]
]
]
--, H.div { "type": "", className: "fa fa-plus-circle fa-lg"} []
--, H.div { "type": "", className: "fa fa-minus-circle fa-lg"} []
-- TODO same as the one in the Login Modal (same CSS)
......@@ -85,10 +111,7 @@ plusCpt = here.component "plus" cpt where
-- from current url
_ <- T.write Nothing backend
T.write_ true showLogin
title = "Add or remove connections to the server(s)."
divClass = "fa fa-universal-access"
buttonClass handed' =
"btn btn-primary d-block " <> switchHanded "mr-1 ml-auto" "ml-1 mr-auto" handed'
forestLayout :: R2.Component Props
forestLayout = R.createElement forestLayoutCpt
......@@ -100,8 +123,10 @@ forestLayoutCpt = here.component "forestLayout" cpt where
[
H.div { className: "forest-layout" }
[
H.div { className: "forest-layout-top-teaser" } []
,
forest p []
,
H.div { className: "forest-layout-teaser" } []
H.div { className: "forest-layout-bottom-teaser" } []
]
]
module Gargantext.Components.MainPage where
import Gargantext.Prelude
import Reactix as R
import Reactix.DOM.HTML as H
import Toestand as T
import Gargantext.Components.App.Data (Boxes)
import Gargantext.Utils.Reactix as R2
here :: R2.Here
here = R2.here "Gargantext.Components.MainPage"
type MainPage =
(
boxes :: Boxes
)
mainPage :: R2.Component MainPage
mainPage = R.createElement mainPageCpt
mainPageCpt :: R.Component MainPage
mainPageCpt = here.component "mainPage" cpt
where
cpt { boxes: { handed
, route } } children = do
handed' <- T.useLive T.unequal handed
route' <- T.useLive T.unequal route
pure $
H.div { id: "page-wrapper" }
[
H.div { className: "container-fluid" } children
]
......@@ -8,21 +8,21 @@ import Data.List as List
import Data.Maybe (Maybe(..), fromMaybe)
import Data.Show.Generic (genericShow)
import Effect (Effect)
import Effect.Aff (Aff, launchAff_, throwError)
import Effect.Class (liftEffect)
import Effect.Aff (Aff, throwError)
import Effect.Exception (error)
import Gargantext.Components.App.Data (Boxes)
import Gargantext.Components.CodeEditor as CE
import Gargantext.Components.FolderView as FV
import Gargantext.Components.InputWithEnter (inputWithEnter)
import Gargantext.Components.Node (NodePoly(..), HyperdataList)
import Gargantext.Components.Nodes.Corpus.Types (CorpusData, Hyperdata(..))
import Gargantext.Components.Nodes.Types (FTField, FTFieldList(..), FTFieldWithIndex, FTFieldsWithIndex(..), Field(..), FieldType(..), Hash, Index, defaultField, defaultHaskell', defaultJSON', defaultMarkdown', defaultPython')
import Gargantext.Components.Nodes.Corpus.Types (CorpusData, Hyperdata)
import Gargantext.Components.Nodes.Types (FTField, FTFieldWithIndex, FTFieldsWithIndex(..), Field(..), FieldType(..), Hash, Index, defaultHaskell', defaultJSON', defaultMarkdown', defaultPython')
import Gargantext.Components.TileMenu (tileMenu)
import Gargantext.Config.REST (RESTError(..))
import Gargantext.Data.Array as GDA
import Gargantext.Hooks.Loader (useLoader)
import Gargantext.Prelude (class Eq, class Show, Unit, bind, discard, pure, show, unit, ($), (+), (-), (<), (<$>), (<<<), (<>), (==), (>))
import Gargantext.Prelude (class Eq, class Show, Unit, bind, discard, pure, show, unit, ($), (<>), const, (<<<), (+), (==), (-), (<), (>), (<$>))
import Gargantext.Routes (SessionRoute(Children, NodeAPI))
import Gargantext.Routes as GR
import Gargantext.Sessions (Session, get, put, sessionId)
import Gargantext.Types (AffETableResult, NodeType(..))
import Gargantext.Utils.Crypto as Crypto
......@@ -51,7 +51,7 @@ corpusLayoutCpt = here.component "corpusLayout" cpt where
key = show (sessionId session) <> "-" <> show nodeId
type KeyProps =
( boxes :: Boxes
( boxes :: Boxes
, key :: String
, nodeId :: Int
, session :: Session
......@@ -62,133 +62,40 @@ corpusLayoutMain props = R.createElement corpusLayoutMainCpt props []
corpusLayoutMainCpt :: R.Component KeyProps
corpusLayoutMainCpt = here.component "corpusLayoutMain" cpt
where
cpt { boxes, key, nodeId, session } _ = do
viewType <- T.useBox Folders
pure $ H.div {} [
H.div {} [
H.div { className: "row" } [
H.div { className: "col-1" } [ viewTypeSelector {state: viewType} ]
, H.div { className: "col-1" } [ FV.homeButton ]
]
]
, H.div {} [corpusLayoutSelection { boxes, key, session, state: viewType, nodeId }]
]
type SelectionProps =
( boxes :: Boxes
, nodeId :: Int
, key :: String
, session :: Session
, state :: T.Box ViewType
)
corpusLayoutSelection :: R2.Leaf SelectionProps
corpusLayoutSelection props = R.createElement corpusLayoutSelectionCpt props []
corpusLayoutSelectionCpt :: R.Component SelectionProps
corpusLayoutSelectionCpt = here.component "corpusLayoutSelection" cpt where
cpt { boxes, key, nodeId, session, state } _ = do
state' <- T.useLive T.unequal state
viewType <- T.read state
pure $ renderContent viewType nodeId session key boxes
renderContent Folders nodeId session _ boxes =
FV.folderView { backFolder: true
, boxes
, nodeId
, session
}
renderContent Code nodeId session key _ = corpusLayoutWithKey { key, nodeId, session }
type CorpusKeyProps =
( nodeId :: Int
, key :: String
, session :: Session
)
corpusLayoutWithKey :: R2.Leaf CorpusKeyProps
corpusLayoutWithKey props = R.createElement corpusLayoutWithKeyCpt props []
corpusLayoutWithKeyCpt :: R.Component CorpusKeyProps
corpusLayoutWithKeyCpt = here.component "corpusLayoutWithKey" cpt where
cpt { nodeId, session } _ = do
reload <- T.useBox T2.newReload
reload' <- T.useLive T.unequal reload
useLoader { errorHandler
, loader: loadCorpusWithReload
, path: { nodeId, reload: reload', session }
, render: \corpus -> corpusLayoutView { corpus, nodeId, reload, session } }
where
errorHandler err = here.log2 "[corpusLayoutWithKey] RESTError" err
type ViewProps =
( corpus :: NodePoly Hyperdata
, nodeId :: Int
, reload :: T2.ReloadS
, session :: Session
)
corpusLayoutView :: Record ViewProps -> R.Element
corpusLayoutView props = R.createElement corpusLayoutViewCpt props []
corpusLayoutViewCpt :: R.Component ViewProps
corpusLayoutViewCpt = here.component "corpusLayoutView" cpt
where
cpt {corpus: (NodePoly {hyperdata: Hyperdata {fields: FTFieldList fields}}), nodeId, reload, session} _ = do
let fieldsWithIndex = FTFieldsWithIndex $ List.mapWithIndex (\idx -> \ftField -> { idx, ftField }) fields
fieldsS <- T.useBox fieldsWithIndex
fields' <- T.useLive T.unequal fieldsS
fieldsRef <- R.useRef fields
-- handle props change of fields
R.useEffect1' fields $ do
if R.readRef fieldsRef == fields then
pure unit
else do
R.setRef fieldsRef fields
T.write_ fieldsWithIndex fieldsS
pure $ H.div {}
[ H.div { className: "row" }
[ H.div { className: "btn btn-primary " <> (saveEnabled fieldsWithIndex fields')
, on: { click: onClickSave {fields: fields', nodeId, reload, session} }
}
[ H.span { className: "fa fa-floppy-o" } [ ] ]
]
, H.div {}
[ fieldsCodeEditor { fields: fieldsS
, nodeId
, session } [] ]
, H.div { className: "row" }
[ H.div { className: "btn btn-primary"
, on: { click: onClickAdd fieldsS }
}
[ H.span { className: "fa fa-plus" } [ ]
cpt { boxes, nodeId, session } _ = do
-- Computed
corpusCodeRoute <- pure $ const do
pure $ GR.CorpusCode (sessionId session) nodeId
-- Render
pure $
H.div {}
[
tileMenu
{ boxes
, currentTile: Just corpusCodeRoute
, xTile: Just corpusCodeRoute
, yTile: Just corpusCodeRoute
}
[
H.button
{ className: "btn btn-primary" }
[
H.i { className: "fa fa-code" } []
]
]
,
H.hr {}
,
FV.folderView
{ nodeId
, session
, backFolder: true
, boxes
}
]
saveEnabled :: FTFieldsWithIndex -> FTFieldsWithIndex -> String
saveEnabled fs fsS = if fs == fsS then "disabled" else "enabled"
onClickSave :: forall e. { fields :: FTFieldsWithIndex
, nodeId :: Int
, reload :: T2.ReloadS
, session :: Session } -> e -> Effect Unit
onClickSave {fields: FTFieldsWithIndex fields, nodeId, reload, session} _ = do
launchAff_ do
res <- saveCorpus $ { hyperdata: Hyperdata {fields: FTFieldList $ (_.ftField) <$> fields}
, nodeId
, session }
liftEffect $ do
_ <- case res of
Left err -> here.log2 "[corpusLayoutView] onClickSave RESTError" err
_ -> pure unit
T2.reload reload
onClickAdd :: forall e. T.Box FTFieldsWithIndex -> e -> Effect Unit
onClickAdd fieldsS _ = do
T.modify_ (\(FTFieldsWithIndex fs) -> FTFieldsWithIndex $
List.snoc fs $ { idx: List.length fs, ftField: defaultField }) fieldsS
-----------------------------------
type FieldsCodeEditorProps =
......@@ -272,11 +179,11 @@ fieldCodeEditorWrapper props = R.createElement fieldCodeEditorWrapperCpt props [
fieldCodeEditorWrapperCpt :: R.Component FieldCodeEditorProps
fieldCodeEditorWrapperCpt = here.component "fieldCodeEditorWrapperCpt" cpt
where
cpt props@{canMoveDown, canMoveUp, field: Field {name, typ}, onMoveDown, onMoveUp, onRemove, onRename} _ = do
pure $ H.div { className: "row card" } [
cpt props@{canMoveDown, canMoveUp, field: Field { name }, onMoveDown, onMoveUp, onRemove, onRename} _ = do
pure $ H.div { className: "card mb-3" } [
H.div { className: "card-header" } [
H.div { className: "code-editor-heading row" } [
H.div { className: "col-4" } [
H.div { className: "code-editor-heading row no-gutters justify-content-between" } [
H.div { className: "col-5" } [
inputWithEnter { onBlur: onRename
, onEnter: \_ -> pure unit
, onValueChanged: onRename
......@@ -286,9 +193,8 @@ fieldCodeEditorWrapperCpt = here.component "fieldCodeEditorWrapperCpt" cpt
, placeholder: "Enter file name"
, type: "text" }
]
, H.div { className: "col-7" } []
, H.div { className: "buttons-right col-1" } ([
H.div { className: "btn btn-danger"
, H.div { className: "d-flex flex-column" } ([
H.div { className: "btn btn-danger mb-1"
, on: { click: \_ -> onRemove unit }
} [
H.span { className: "fa fa-trash" } [ ]
......
module Gargantext.Components.Nodes.Corpus.Code where
import Data.Either (Either(..))
import Data.List as List
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Aff (launchAff_)
import Effect.Class (liftEffect)
import Gargantext.Components.App.Data (Boxes)
import Gargantext.Components.Node (NodePoly(..))
import Gargantext.Components.Nodes.Corpus (fieldsCodeEditor, loadCorpusWithReload, saveCorpus)
import Gargantext.Components.Nodes.Corpus.Types (Hyperdata(..))
import Gargantext.Components.Nodes.Types (FTFieldList(..), FTFieldsWithIndex(..), defaultField)
import Gargantext.Components.TileMenu (tileMenu)
import Gargantext.Hooks.Loader (useLoader)
import Gargantext.Prelude (Unit, bind, discard, pure, unit, ($), (<$>), (<>), (==), const)
import Gargantext.Routes as GR
import Gargantext.Sessions (Session, sessionId)
import Gargantext.Utils.Reactix as R2
import Gargantext.Utils.Toestand as T2
import Reactix as R
import Reactix.DOM.HTML as H
import Toestand as T
here :: R2.Here
here = R2.here "Gargantext.Components.Nodes.Corpus.Code"
type Props =
( nodeId :: Int
, session :: Session
, boxes :: Boxes
)
type ViewProps =
( corpus :: NodePoly Hyperdata
, nodeId :: Int
, reload :: T2.ReloadS
, session :: Session
, boxes :: Boxes
)
corpusCodeLayout :: R2.Leaf Props
corpusCodeLayout props = R.createElement corpusCodeLayoutCpt props []
corpusCodeLayoutCpt :: R.Component Props
corpusCodeLayoutCpt = here.component "corpusCodeLayout" cpt where
cpt { nodeId, session, boxes } _ = do
reload <- T.useBox T2.newReload
reload' <- T.useLive T.unequal reload
useLoader { errorHandler
, loader: loadCorpusWithReload
, path: { nodeId, reload: reload', session }
, render: \corpus -> corpusCodeView { corpus, nodeId, reload, session, boxes } }
where
errorHandler err = here.log2 "[corpusLayoutWithKey] RESTError" err
corpusCodeView :: Record ViewProps -> R.Element
corpusCodeView props = R.createElement corpusCodeViewCpt props []
corpusCodeViewCpt :: R.Component ViewProps
corpusCodeViewCpt = here.component "corpusCodeView" cpt where
cpt {corpus: (NodePoly {hyperdata: Hyperdata {fields: FTFieldList fields}}), nodeId, reload, session, boxes} _ = do
let fieldsWithIndex = FTFieldsWithIndex $ List.mapWithIndex (\idx -> \ftField -> { idx, ftField }) fields
fieldsS <- T.useBox fieldsWithIndex
fields' <- T.useLive T.unequal fieldsS
fieldsRef <- R.useRef fields
-- handle props change of fields
R.useEffect1' fields $ do
if R.readRef fieldsRef == fields then
pure unit
else do
R.setRef fieldsRef fields
T.write_ fieldsWithIndex fieldsS
corpusRoute <- pure $ const do
pure $ GR.Corpus (sessionId session) nodeId
pure $
H.div
{}
[
tileMenu
{ boxes
, currentTile: Just corpusRoute
, xTile: Just corpusRoute
, yTile: Just corpusRoute
}
[
H.button
{ className: "btn btn-primary" }
[
H.i { className: "fa fa-folder" } []
]
]
,
H.hr {}
,
H.div
{ className: "mb-4" }
[
H.div
{ className: "btn btn-primary " <> (saveEnabled fieldsWithIndex fields')
, on: { click: onClickSave {fields: fields', nodeId, reload, session} }
}
[ H.span { className: "fa fa-floppy-o" } [ ] ]
]
,
H.div
{}
[
fieldsCodeEditor
{ fields: fieldsS
, nodeId
, session }
[]
]
,
H.div
{ className: "mb-4" }
[
H.div
{ className: "btn btn-primary"
, on: { click: onClickAdd fieldsS }
}
[ H.span { className: "fa fa-plus" } [ ] ]
]
]
saveEnabled :: FTFieldsWithIndex -> FTFieldsWithIndex -> String
saveEnabled fs fsS = if fs == fsS then "disabled" else "enabled"
onClickSave :: forall e. { fields :: FTFieldsWithIndex
, nodeId :: Int
, reload :: T2.ReloadS
, session :: Session } -> e -> Effect Unit
onClickSave {fields: FTFieldsWithIndex fields, nodeId, reload, session} _ = do
launchAff_ do
res <- saveCorpus $ { hyperdata: Hyperdata {fields: FTFieldList $ (_.ftField) <$> fields}
, nodeId
, session }
liftEffect $ do
_ <- case res of
Left err -> here.log2 "[corpusLayoutView] onClickSave RESTError" err
_ -> pure unit
T2.reload reload
onClickAdd :: forall e. T.Box FTFieldsWithIndex -> e -> Effect Unit
onClickAdd fieldsS _ = do
T.modify_ (\(FTFieldsWithIndex fs) -> FTFieldsWithIndex $
List.snoc fs $ { idx: List.length fs, ftField: defaultField }) fieldsS
......@@ -107,7 +107,6 @@ frameLayoutViewCpt = here.component "frameLayoutView" cpt
_ ->
pure $ H.div{} [
FV.backButton
, FV.homeButton
, H.div { className : "frame"
, rows: "100%,*" }
[ -- H.script { src: "https://visio.gargantext.org/external_api.js"} [],
......@@ -141,7 +140,7 @@ nodeFrameVisioCpt = here.component "nodeFrameVisio" cpt
Just r -> do
api <- JM.jitsiMeetAPI (WURL.host url) { parentNode: r, roomName: frame_id }
here.log2 "[nodeFrameVisio] api" api
pure $ H.div { ref } [ H.text $ WURL.host url ]
type LoadProps = ( nodeId :: Int
......
......@@ -2,9 +2,14 @@ module Gargantext.Components.Router (router) where
import Gargantext.Prelude
import Data.Array (filter, length)
import Data.Array as A
import Data.Foldable (intercalate)
import Data.Maybe (Maybe(..))
import Data.Tuple.Nested ((/\))
import Data.UUID (UUID)
import Data.UUID as UUID
import Effect (Effect)
import Gargantext.Components.App.Data (Boxes)
import Gargantext.Components.ErrorsView (errorsView)
import Gargantext.Components.Footer (footer)
......@@ -15,11 +20,11 @@ import Gargantext.Components.GraphExplorer.Sidebar.Types as GEST
import Gargantext.Components.GraphExplorer.TopBar as GETB
import Gargantext.Components.Lang (LandingLang(LL_EN))
import Gargantext.Components.Login (login)
import Gargantext.Components.MainPage as MainPage
import Gargantext.Components.Nodes.Annuaire (annuaireLayout)
import Gargantext.Components.Nodes.Annuaire.User (userLayout)
import Gargantext.Components.Nodes.Annuaire.User.Contact (contactLayout)
import Gargantext.Components.Nodes.Corpus (corpusLayout)
import Gargantext.Components.Nodes.Corpus.Code (corpusCodeLayout)
import Gargantext.Components.Nodes.Corpus.Dashboard (dashboardLayout)
import Gargantext.Components.Nodes.Corpus.Document (documentMainLayout)
import Gargantext.Components.Nodes.Corpus.Phylo (phyloLayout)
......@@ -28,10 +33,11 @@ import Gargantext.Components.Nodes.Frame (frameLayout)
import Gargantext.Components.Nodes.Home (homeLayout)
import Gargantext.Components.Nodes.Lists as Lists
import Gargantext.Components.Nodes.Texts as Texts
import Gargantext.Components.Tile (tileBlock)
import Gargantext.Components.TopBar as TopBar
import Gargantext.Config (defaultFrontends, defaultBackends)
import Gargantext.Ends (Backend)
import Gargantext.Routes (AppRoute)
import Gargantext.Routes (AppRoute, Tile)
import Gargantext.Routes as GR
import Gargantext.Sessions (Session, WithSession)
import Gargantext.Sessions as Sessions
......@@ -39,9 +45,11 @@ import Gargantext.Types (CorpusId, Handed(..), ListId, NodeID, NodeType(..), Ses
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
import Record (get)
import Record as Record
import Record.Extra as RE
import Toestand as T
import Type.Proxy (Proxy(..))
here :: R2.Here
here = R2.here "Gargantext.Components.Router"
......@@ -104,7 +112,89 @@ mainPage p = R.createElement mainPageCpt p []
mainPageCpt :: R.Component Props
mainPageCpt = here.component "mainPage" cpt where
cpt { boxes } _ = do
pure $ MainPage.mainPage { boxes } [ renderRoute { boxes } ]
-- States
route <- R2.useLive' boxes.route
tileAxisXList <- R2.useLive' boxes.tileAxisXList
tileAxisYList <- R2.useLive' boxes.tileAxisYList
-- Computed
let
findTile :: UUID -> Record Tile -> Boolean
findTile id tile = eq id $ get (Proxy :: Proxy "id") tile
deleteTile :: Record Tile -> T.Box (Array (Record Tile)) -> (Unit -> Effect Unit)
deleteTile tile listBox = const do
list <- T.read listBox
newList <- pure $ filter (_ # tile.id # findTile # not) list
T.write_ newList listBox
let hasHorizontalTiles = not $ eq 0 $ length tileAxisXList
let hasVerticalTiles = not $ eq 0 $ length tileAxisYList
-- Render
pure $
H.div { className: "main-page" }
[
H.div
{ className: intercalate " "
[ "main-page__main-row"
, if (hasVerticalTiles)
then "main-page__main-row--with-y-tiles"
else ""
, if (hasVerticalTiles && not hasHorizontalTiles)
then "main-page__main-row--only-y-tiles"
else ""
]
}
[
-- main render route
H.div { className: "main-page__main-route" }
[
renderRoute { boxes, route }
]
,
-- optional tile render route [Y Axis ~ aside vertical column]
case tileAxisYList of
[] -> mempty
_ ->
H.div
{ className: intercalate " "
[ "main-page__vertical-tiles"
, "main-page__vertical-tiles--" <> (show $ length tileAxisYList)
]
} $
tileAxisYList <#> \tile -> tileBlock
{ boxes
, tile
, key: UUID.toString tile.id
, closeCallback: deleteTile tile boxes.tileAxisYList
}
[
renderRoute { boxes, route: tile.route }
]
]
,
-- optional tile render route [X Axis ~ bottom horizontal row]
case tileAxisXList of
[] -> mempty
_ ->
H.div
{ className: intercalate " "
[ "main-page__horizontal-tiles"
, "main-page__horizontal-tiles--" <> (show $ length tileAxisXList)
]
} $
tileAxisXList <#> \tile -> tileBlock
{ boxes
, tile
, key: UUID.toString tile.id
, closeCallback: deleteTile tile boxes.tileAxisXList
}
[
renderRoute { boxes, route: tile.route }
]
]
forest :: R2.Leaf Props
forest p = R.createElement forestCpt p []
......@@ -137,20 +227,28 @@ sidePanelCpt = here.component "sidePanel" cpt where
Opened -> pure $ openedSidePanel (Record.merge { session: s } props) []
_ -> pure $ H.div {} []
renderRoute :: R2.Leaf Props
type RenderRouteProps =
( route :: AppRoute
| Props
)
renderRoute :: R2.Leaf RenderRouteProps
renderRoute p = R.createElement renderRouteCpt p []
renderRouteCpt :: R.Component Props
renderRouteCpt :: R.Component RenderRouteProps
renderRouteCpt = here.component "renderRoute" cpt where
cpt props@{ boxes } _ = do
let sessionNodeProps sId nId = Record.merge { nodeId: nId, sessionId: sId } props
route' <- T.useLive T.unequal boxes.route
cpt { boxes, route } _ = do
let sessionNodeProps sId nId =
{ nodeId: nId
, sessionId: sId
, boxes
}
pure $ R.fragment
[ case route' of
[ case route of
GR.Annuaire s n -> annuaire (sessionNodeProps s n) []
GR.ContactPage s a n -> contact (Record.merge { annuaireId: a } $ sessionNodeProps s n) []
GR.Corpus s n -> corpus (sessionNodeProps s n) []
GR.CorpusCode s n -> corpusCode (sessionNodeProps s n) []
GR.CorpusDocument s c l n -> corpusDocument (Record.merge { corpusId: c, listId: l } $ sessionNodeProps s n) []
GR.Dashboard s n -> dashboard (sessionNodeProps s n) []
GR.Document s l n -> document (Record.merge { listId: l } $ sessionNodeProps s n) []
......@@ -259,6 +357,25 @@ corpusCpt = here.component "corpus" cpt where
, nodeId
, session } } sessionProps) []
corpusCode :: R2.Component SessionNodeProps
corpusCode = R.createElement corpusCodeCpt
corpusCodeCpt :: R.Component SessionNodeProps
corpusCodeCpt = here.component "corpusCode" cpt where
cpt props@{ boxes, nodeId } _ = do
let
sessionProps = RE.pick props :: Record SessionProps
authedProps = Record.merge
{ content: \session -> corpusCodeLayout
{ nodeId
, session
, boxes
}
}
sessionProps
pure $ authed authedProps []
type CorpusDocumentProps =
( corpusId :: CorpusId
, listId :: ListId
......
......@@ -64,7 +64,7 @@ tableHeaderLayoutCpt = here.component "tableHeaderLayout" cpt
cacheState' <- T.useLive T.unequal cacheState
pure $ R.fragment
[ R2.row [FV.backButton, FV.homeButton]
[ R2.row [FV.backButton]
,
R2.row
[ H.div {className: "col-md-3"} [ H.h3 {} [H.text title] ]
......@@ -111,7 +111,7 @@ tableHeaderLayoutCpt = here.component "tableHeaderLayout" cpt
cacheStateToggle NT.CacheOn = NT.CacheOff
cacheStateToggle NT.CacheOff = NT.CacheOn
table :: R2.Leaf Props
table props = R.createElement tableCpt props []
tableCpt :: R.Component Props
......@@ -160,7 +160,7 @@ type FilterRowsParams =
)
filterRows :: forall a. Record FilterRowsParams -> Seq.Seq a -> Seq.Seq a
filterRows { params: { limit, offset, orderBy } } rs = newRs
filterRows { params: { limit, offset } } rs = newRs
where
newRs = Seq.take limit $ Seq.drop offset $ rs
......@@ -325,11 +325,3 @@ string2PageSize "50" = PS50
string2PageSize "100" = PS100
string2PageSize "200" = PS200
string2PageSize _ = PS10
'use strict';
/**
* @param {DOMElement} element
* @param {string} attribute
* @param {string} value
* @returns
*/
exports.setAttribute = function(element) {
return function(attribute) {
return function(value) {
return function() {
element.setAttribute(attribute, value);
}
}
}
}
module Gargantext.Components.Themes where
import Gargantext.Prelude
import DOM.Simple (document)
import Data.Array as A
import Data.Generic.Rep (class Generic)
import Data.Eq.Generic (genericEq)
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Data.Nullable (toMaybe)
import Effect (Effect)
import FFI.Simple ((.=))
import FFI.Simple ((...), (.=))
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
import Toestand as T
import Gargantext.Prelude
import Gargantext.Utils.Reactix as R2
-- (?) Unknown runtime DOM errors lead to a FFI workaround for setting the
-- property of the element (see `markThemeToDOMTree` method)
--
-- Both use cases throw the error:
--
-- ```
-- TypeError: FFI_Simple_Functions.applyMethod'(...)(...)(...) is not a function
-- ```
--
-- ```purescript
-- _ <- el ... "setAttribute" $ [ "data-theme", name ]
-- _ <- pure $ (el .= "data-theme") name
-- ```
foreign import setAttribute :: R.Element -> String -> String -> Effect Unit
here :: R2.Here
here = R2.here "Gargantext.Components.Themes"
......@@ -60,6 +77,14 @@ switchTheme (Theme { location }) = do
_ <- pure $ (el .= "href") location
pure unit
markThemeToDOMTree :: Theme -> Effect Unit
markThemeToDOMTree (Theme { name }) = do
mEl <- pure $ toMaybe (document ... "getElementById" $ [ "app" ])
case mEl of
Nothing -> pure unit
Just el -> setAttribute el "data-theme" name
type ThemeSwitcherProps = (
theme :: Theme
, themes :: Array Theme
......@@ -78,6 +103,8 @@ themeSwitcherCpt = here.component "themeSwitcher" cpt
let option (Theme { name }) = H.option { value: name } [ H.text name ]
let options = map option themes
R.useEffectOnce' $ markThemeToDOMTree currentTheme'
pure $ R2.select { className: "form-control"
, defaultValue: themeName currentTheme'
, on: { change: onChange currentTheme } } options
......@@ -90,4 +117,5 @@ themeSwitcherCpt = here.component "themeSwitcher" cpt
Nothing -> pure unit
Just t -> do
switchTheme t
markThemeToDOMTree t
T.write_ t currentTheme
module Gargantext.Components.Tile
( tileBlock
, tileContext
) where
import Gargantext.Prelude
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Gargantext.Components.App.Data (Boxes)
import Gargantext.Routes (Tile)
import Reactix as R
import Reactix.DOM.HTML as H
type Props =
( boxes :: Boxes
, tile :: Record Tile
, key :: String
, closeCallback :: Unit -> Effect Unit
)
tileBlock :: Record Props -> Array R.Element -> R.Element
tileBlock = R.createElement tileBlockCpt
tileBlockCpt :: R.Component Props
tileBlockCpt = R.hooksComponent "tileBlock" cpt where
cpt props@{ closeCallback } children = do
-- Render
pure $
R.provideContext tileContext (Just props)
[
H.div
{ className: "tile-block" }
[
H.div { className: "tile-block__header"}
[
H.i
{ className: "btn fa fa-times"
, on: { click: closeCallback }
}
[]
]
,
H.div { className: "tile-block__body" }
children
]
]
tileContext :: R.Context (Maybe (Record Props))
tileContext = R.createContext Nothing
module Gargantext.Components.TileMenu
( tileMenu
) where
import Gargantext.Prelude
import Data.Array (snoc)
import Data.Maybe (Maybe(..))
import Data.Nullable (null)
import Data.UUID as UUID
import Effect (Effect)
import Gargantext.Components.App.Data (Boxes)
import Gargantext.Hooks.LinkHandler (useLinkHandler)
import Gargantext.Routes (AppRoute, Tile)
import Gargantext.Utils.Popover as Popover
import Reactix as R
import Reactix.DOM.HTML as H
import Toestand as T
type Props =
( boxes :: Boxes
, currentTile :: Maybe (Unit -> Effect AppRoute)
, xTile :: Maybe (Unit -> Effect AppRoute)
, yTile :: Maybe (Unit -> Effect AppRoute)
)
tileMenu :: Record Props -> Array (R.Element) -> R.Element
tileMenu = R.createElement tileMenuCpt
tileMenuCpt :: R.Component Props
tileMenuCpt = R.hooksComponent "tileMenu" cpt where
cpt props@{ boxes } children = do
-- Hooks
{ goToRoute } <- useLinkHandler
-- States
popoverRef <- R.useRef null
-- Helpers
let
newTile :: T.Box (Array (Record Tile)) -> AppRoute -> Effect Unit
newTile list route = do
id <- UUID.genUUID
tile <- pure { id, route }
T.modify_ (\arr -> snoc arr tile) list
currentTileCbk :: (Unit -> Effect AppRoute) -> Effect Unit
currentTileCbk thunk = thunk unit >>= goToRoute
addTileCbk ::
(Unit -> Effect AppRoute)
-> T.Box (Array (Record Tile))
-> Effect Unit
addTileCbk thunk list = thunk unit >>= newTile list
-- Render
pure $
H.div { className: "tile-menu" }
[
Popover.popover
{ arrow : false
, open : false
, onClose : const $ pure unit
, onOpen : const $ pure unit
, ref : popoverRef
}
[
R.fragment children
,
H.div { className: "tile-menu__popover" }
[
H.ul {}
[
-- Current Tile
case props.currentTile of
Nothing -> mempty
Just thunk ->
H.li { className: "tile-menu__item"}
[
H.button
{ className: "btn btn-link"
, on: { click: const do
currentTileCbk thunk
Popover.setOpen popoverRef false
}
}
[
H.i { className: "fa fa-share" } []
,
H.text "open on current tile"
]
]
,
-- Add vertical tile
case props.yTile of
Nothing -> mempty
Just thunk ->
H.li { className: "tile-menu__item" }
[
H.button
{ className: "btn btn-link"
, on: { click: const do
addTileCbk thunk boxes.tileAxisYList
Popover.setOpen popoverRef false
}
}
[
H.i { className: "fa fa-caret-square-o-right" } []
,
H.text "open from a new tile"
]
]
,
-- Add horizontal tile
case props.xTile of
Nothing -> mempty
Just thunk ->
H.li { className: "tile-menu__item" }
[
H.button
{ className: "btn btn-link"
, on: { click: const do
addTileCbk thunk boxes.tileAxisXList
Popover.setOpen popoverRef false
}
}
[
H.i { className: "fa fa-caret-square-o-down" } []
,
H.text "open from a new tile"
]
]
]
]
]
]
......@@ -22,4 +22,3 @@ handleRESTError errors (Left error) _ = liftEffect $ do
T.modify_ (A.cons $ FRESTError { error }) errors
here.log2 "[handleTaskError] RESTError" error
handleRESTError _ (Right task) handler = handler task
module Gargantext.Hooks.LinkHandler
( useLinkHandler
, goToRoute, goToURL, goToPreviousPage
) where
import Gargantext.Prelude
import Data.Array (findIndex, modifyAtIndices, singleton)
import Data.Maybe (Maybe(..))
import Data.UUID (UUID)
import Effect (Effect)
import Gargantext.Components.Tile (tileContext)
import Gargantext.Routes (AppRoute, Tile, appPath)
import Reactix as R
import Record (get, set)
import Toestand as T
import Type.Proxy (Proxy(..))
import Web.HTML (window)
import Web.HTML.History (back)
import Web.HTML.Location (assign, setHref)
import Web.HTML.Window (history, location)
type Methods =
( goToRoute :: AppRoute -> Effect Unit
, goToPreviousPage :: Unit -> Effect Unit
, goToURL :: String -> Effect Unit
)
useLinkHandler :: R.Hooks (Record Methods)
useLinkHandler = do
-- retrieve tile context where this hook is called
-- if the callee is within a tile, some of the hook methods will be altered
mTileContext <- R.useContext tileContext
pure
{ goToRoute : case mTileContext of
Nothing -> goToRoute
Just { boxes, tile } -> changeTileRoute
boxes.tileAxisXList
boxes.tileAxisYList
tile
, goToPreviousPage : const goToPreviousPage
, goToURL : goToURL
}
-- (?) Also exporting implementation methods, as it can be useful in an
-- outside-of-hook context
goToRoute :: AppRoute -> Effect Unit
goToRoute route = window >>= location >>= (assign $ "/#/" <> appPath route)
goToPreviousPage :: Effect Unit
goToPreviousPage = window >>= history >>= back
goToURL :: String -> Effect Unit
goToURL url = window >>= location >>= setHref url
--------------------------------------
changeTileRoute ::
T.Box (Array (Record Tile))
-> T.Box (Array (Record Tile))
-> Record Tile
-> AppRoute
-> Effect Unit
changeTileRoute tileAxisXList tileAxisYList tile newRoute = do
listX <- T.read tileAxisXList
listY <- T.read tileAxisYList
let
findTile :: UUID -> Record Tile -> Boolean
findTile id tile' = eq id $ get (Proxy :: Proxy "id") tile'
hasTile :: Array (Record Tile) -> UUID -> Maybe Int
hasTile list id = findIndex (_ # id # findTile) list
updateTile :: Int -> AppRoute -> Array (Record Tile) -> Array (Record Tile)
updateTile index route list = modifyAtIndices
(singleton index)
(set (Proxy :: Proxy "route") route $ _)
list
-- (!) to optimize when tile structure design is locked
case hasTile listX tile.id of
Nothing -> pure unit
Just index -> T.write_ (updateTile index newRoute listX) tileAxisXList
case hasTile listY tile.id of
Nothing -> pure unit
Just index -> T.write_ (updateTile index newRoute listY) tileAxisYList
module Gargantext.Router where
import Prelude
import Data.Foldable (oneOf)
import Data.Int (floor)
import Routing.Match (Match, lit, num, str)
import Gargantext.Routes (AppRoute(..))
import Gargantext.Types (SessionId(..))
import Routing.Match (Match, lit, num, str)
router :: Match AppRoute
router = oneOf
......@@ -20,7 +20,8 @@ router = oneOf
<*> (lit "list" *> int)
<*> (lit "document" *> int)
, Corpus <$> (route "corpus" *> sid) <*> int
, Document <$> (route "list" *> sid) <*> int
, CorpusCode <$> (route "corpusCode" *> sid) <*> int
, Document <$> (route "list" *> sid) <*> int
<*> (lit "document" *> int)
, Dashboard <$> (route "dashboard" *> sid) <*> int
, PGraphExplorer <$> (route "graph" *> sid) <*> int
......@@ -47,4 +48,3 @@ router = oneOf
sid :: Match SessionId
sid = SessionId <$> str
......@@ -3,16 +3,15 @@ module Gargantext.Routes where
import Prelude
import Data.Maybe (Maybe(..))
import Gargantext.Types (ChartOpts, ChartType, CorpusMetricOpts, CTabNgramType, Id, Limit,
ListId, DocId, ContactId, NgramsGetOpts, NgramsGetTableAllOpts, NodeType,
Offset, OrderBy, SearchOpts, SessionId, TabSubType, TabType, TermList)
import Data.UUID (UUID)
import Gargantext.Types (ChartOpts, ChartType, CorpusMetricOpts, CTabNgramType, Id, Limit, ListId, DocId, NgramsGetOpts, NgramsGetTableAllOpts, NodeType, Offset, OrderBy, SearchOpts, SessionId, TabSubType, TabType, TermList)
import Gargantext.Types as GT
data AppRoute
= Annuaire SessionId Int
| ContactPage SessionId Int Int
| Corpus SessionId Int
| CorpusCode SessionId Int
| CorpusDocument SessionId Int Int Int
| Dashboard SessionId Int
| Document SessionId Int Int
......@@ -69,6 +68,7 @@ instance Show AppRoute where
show (FolderShared s i) = "FolderShared" <> show i <> " (" <> show s <> ")"
show (Team s i) = "Team" <> show i <> " (" <> show s <> ")"
show (Corpus s i) = "Corpus" <> show i <> " (" <> show s <> ")"
show (CorpusCode s i) = "CorpusCode" <> show i <> " (" <> show s <> ")"
show (Document _ s i) = "Document" <> show i <> " (" <> show s <> ")"
show (CorpusDocument s _ _ i) = "CorpusDocument" <> show i <> " (" <> show s <> ")"
show (PGraphExplorer s i) = "graphExplorer" <> show i <> " (" <> show s <> ")"
......@@ -96,6 +96,7 @@ appPath (FolderShared s i) = "folderShared/" <> show s <> "/" <> show i
appPath (Team s i) = "team/" <> show s <> "/" <> show i
appPath (CorpusDocument s c l i) = "corpus/" <> show s <> "/" <> show c <> "/list/" <> show l <> "/document/" <> show i
appPath (Corpus s i) = "corpus/" <> show s <> "/" <> show i
appPath (CorpusCode s i) = "corpusCode/" <> show s <> "/" <> show i
appPath (Document s l i) = "list/" <> show s <> "/" <> show l <> "/document/" <> show i
appPath (Dashboard s i) = "dashboard/" <> show s <> "/" <> show i
appPath (PGraphExplorer s i) = "graph/" <> show s <> "/" <> show i
......@@ -131,3 +132,11 @@ nodeTypeAppRoute GT.NodeFrameWrite s i = Just $ RouteFrameWrite s i
nodeTypeAppRoute GT.NodeFrameCalc s i = Just $ RouteFrameCalc s i
nodeTypeAppRoute GT.NodeFrameVisio s i = Just $ RouteFrameVisio s i
nodeTypeAppRoute _ _ _ = Nothing
------------------------------------------------------
type Tile =
( id :: UUID
, route :: AppRoute
)
......@@ -437,3 +437,25 @@ boundingRect els =
, y: miny
, width: maxx - minx
, height: maxy - miny }
-- | One-liner `if` simplifying render writing
-- | (best for one child)
if' :: Boolean -> R.Element -> R.Element
if' = if _ then _ else mempty
-- | One-liner `if` simplifying render writing
-- | (best for multiple children)
if_ :: Boolean -> Array (R.Element) -> R.Element
if_ pred arr = if pred then (R.fragment arr) else mempty
-- | Toestand `useLive` automatically sets to "unchanged" behavior
useLive' :: forall box b. T.Read box b => Eq b => box -> R.Hooks b
useLive' = T.useLive T.unequal
-- | Toestand `useBox` + `useLive'` shorthand following same patterns as
-- | React StateHooks API
useBox' :: forall b. Eq b => b -> R.Hooks (Tuple b (T.Box b))
useBox' default = do
box <- T.useBox default
b <- useLive' box
pure $ b /\ box
@use "./abstract/_members" as *
@mixin font-inherit()
font-family: inherit
font-size: inherit
......@@ -67,8 +69,10 @@
width: 1px
.html
flex-grow: 2
margin-left: 25px
padding-left: 25px
margin-left: 8px
margin-right: 8px
padding-left: 8px
padding-right: 8px
&.language-haskell
font-family: Fira code,Fira Mono,Consolas,Menlo,Courier,monospace
white-space: pre
......@@ -85,3 +89,9 @@
ol
li
list-style: decimal !important
&__toolbar
margin-bottom: space-x(2.5)
&__type
width: 200px
@use "./abstract/_members" as *
.fv.folders
display: grid
grid-template-columns: 100px 100px 100px
......@@ -12,4 +14,4 @@
.fv.btn
size: 100%
width:100%
\ No newline at end of file
width:100%
......@@ -9,12 +9,6 @@
height: 150px
border: 3px solid white
#page-wrapper
padding-top: 40px
padding-left: 16px
padding-right: 16px
width: 100%
#user-page-header
border-bottom : 1px solid black
......
@use "./abstract/_members" as *
/* styles for menu.html template (navbar etc) */
/* #dafixedtop .navbar-text, #graphsfixedtop .navbar-text
......@@ -52,3 +54,10 @@
.minimsg *
line-height: 100%
.tile-menu
&__item
.fa
margin-right: space-x(1)
@use "./abstract/_members" as *
.cache-toggle
cursor: pointer
.left-handed
......@@ -114,3 +116,62 @@ ul
&.right-handed
flex-direction: row
.main-page
$self: &
$page-padding: space-x(3) space-x(4)
$topbar-height: 56px // ~ unworthy empirical value (@TODO topbar height calculation)
flex-grow: 1 // quick workaround, stretching block when subjected to flex API
&__main-row
display: flex
&--with-y-tiles
#{ $self }__main-route
width: 60%
#{ $self }__vertical-tiles
width: 40%
&--only-y-tiles
// ensure minimum height, so that the border delimiting "main route" and
// its "vertical tiles" will not end abruptly
min-height: calc( 100vh - #{ $topbar-height })
// main route ~ main tile
&__main-route
padding: $page-padding
width: 100%
// optional vertical tiles ~ y axis column
&__vertical-tiles
display: flex
flex-direction: column
// optional horizontal tiles ~ x axis row
&__horizontal-tiles
display: flex
flex-direction: row
// dynamic width according to number of tiles
@for $i from 1 through 10
&--#{ $i } .tile-block
width: calc( 100% / #{ $i })
.tile-block
$tile-padding: space-x(0.5) space-x(2) space-x(1.5)
&__header
display: flex
@include right-handed
justify-content: flex-end
@include left-handed
justify-content: flex-start
&__body
padding: $tile-padding
@use "./abstract/_members" as *
$forest-layout-top-teaser-height: 24px
$forest-layout-bottom-teaser-height: 24px // ~line-height to 1.5 (covering tree)
@mixin forest-layout-gutter
@include right-handed
padding-left: space-x(2)
padding-right: space-x(1)
@include left-handed
padding-left: space-x(1)
padding-right: space-x(2)
/////////////////////////////////////////
body > .tree ul > li:first-child::before
top: 12px
......@@ -28,11 +46,8 @@ li
a.settings
visibility: visible
.forest-layout-content
& > .tree
margin-top: 20px
.tree
ul
li
position: relative
......@@ -220,7 +235,6 @@ li
.forest-layout
$offset-y: 56px
padding-top: 8px
z-index: 909
// make the sidebar a scrollable component
......@@ -241,32 +255,75 @@ li
transition: border 150ms
.forest-layout-action
@include forest-layout-gutter
display: flex
@include right-handed
flex-direction: row
&__button
margin-right: space-x(2)
@include left-handed
flex-direction: row-reverse
&__button
margin-left: space-x(2)
// UX addition: visually delimiting the sidebar on hover
// -- for now in "_common.scss" file (see @TODO: More SASS structure)
// UX best pratice: when a lengthy column is overflowy hidden (with a scroll), a teaser
// UI element shows to the user that a scroll is possible
.forest-layout-teaser
$height: 24px // ~line-height to 1.5
.forest-layout-bottom-teaser
$width: 16.6666666667% // simulate "col-2" sidebar attributes
$minus-parent-border: 1px // border size of the sidebar on hover
z-index: 1 // @TODO z-index stacking context management
pointer-events: none
position: fixed
bottom: 0
height: $height
height: $forest-layout-bottom-teaser-height
width: calc(#{ $width } - #{ $minus-parent-border})
// background -- for now in "_common.scss" file (@TODO: More SASS structure)
.right-handed .forest-layout-teaser
left: 0
@include right-handed
left: 0
@include left-handed
right: 0
.forest-layout-top-teaser
$minus-parent-border: 1px // border size of the sidebar on hover
z-index: 1 // @TODO z-index stacking context management
pointer-events: none
position: sticky
top: 0
height: $forest-layout-top-teaser-height
width: calc(100% - #{ $minus-parent-border})
// background -- for now in "_common.scss" file (@TODO: More SASS structure)
@include right-handed
left: 0
@include left-handed
right: 0
.forest-layout-content
& > ul.tree // `ul` prefixed to override bootstrap "_reboot.scss" rules
@include forest-layout-gutter
.left-handed .forest-layout-teaser
right: 0
margin-top: space-x(3)
margin-bottom: 0
.left-handed .forest-layout
padding-left: 8px
padding-right: 16px
.right-handed .forest-layout
padding-left: 16px
padding-right: 8px
&:last-child
margin-bottom: $forest-layout-bottom-teaser-height
@use "./_variables" as *;
/// Functions
///--------------------------
/// Add alpha channel to a color
/// @access public
/// @param {Color} $color - color to work with
/// @param {Number} $percentage - percentage of `$color` opacity
/// @return {Color} $color
@function mixAlpha($color, $percentage) {
@return rgba($color, $percentage);
}
/// Add spacing length according to given unit
/// @access public
/// @param {Number} $num
/// @return {Size} $em
@function space-x($num) {
@return $num * $space-unit;
}
/// Mixins
///--------------------------
/// Place contextualised element (&) at the root,
/// prefixed with left handed class
/// @access public
@mixin left-handed() {
@at-root .left-handed & {
@content;
}
}
/// Place contextualised element (&) at the root,
/// prefixed with right handed class
/// @access public
@mixin right-handed() {
@at-root .right-handed & {
@content;
}
}
/// Global spacing value
$space-unit: 8px;
/// Abtstract
/// Add alpha channel to a color
/// @access public
/// @param {Color} $color - color to work with
/// @param {Number} $percentage - percentage of `$color` opacity
/// @return {Color}
@function mixAlpha($color, $percentage) {
@return rgba($color, $percentage);
}
@use '../abstract/_members' as main;
/// Misc
///--------------------------
.with-icon-font {
font-family: ForkAwesome, $font-family-base;
......@@ -18,19 +9,54 @@
/// Tree
///--------------------------
/// (?) @TODO: More SASS structure (eg. exporting variables into component
/// (?) @TODO: More SASS structure (eg. exporting theme variables into component
/// SASS files)
/// For now we have cut-copy the rules here
.forest-layout {
.right-handed .forest-layout {
border-right: 1px solid $body-bg;
&:hover { border-right: 1px solid $border-color; }
@include main.right-handed {
border-right: 2px solid $border-color;
}
@include main.left-handed {
border-left: 2px solid $border-color;
}
}
.left-handed .forest-layout {
border-left: 1px solid $body-bg;
&:hover { border-left: 1px solid $border-color; }
.forest-layout-bottom-teaser {
background: linear-gradient(
to bottom,
mixAlpha($body-bg, 0%) 0%,
mixAlpha($body-bg, 100%) 45%
);
}
.forest-layout-teaser {
background: linear-gradient(to bottom, mixAlpha($body-bg, 0%) 0%, mixAlpha($body-bg, 100%) 45%);
.forest-layout-top-teaser {
background: linear-gradient(
to top,
mixAlpha($body-bg, 0%) 0%,
mixAlpha($body-bg, 100%) 45%
);
}
.main-page__horizontal-tiles {
border-top: 2px solid $border-color;
.tile-block:not(:first-child) {
border-left: 2px solid $border-color;
}
}
.main-page__vertical-tiles {
border-left: 2px solid $border-color;
.tile-block:not(:first-child) {
border-top: 2px solid $border-color;
}
}
.tile-menu__popover {
border-radius: $border-radius;
background-color: $body-bg;
}
......@@ -74,6 +74,7 @@ $breadcrumb-active-color:$gray-500;
@import "../../../node_modules/bootstrap/scss/bootstrap";
// Add SASS theme customizations here..
@import "../abstract/_members";
@import "./common";
.navbar-dark.bg-primary {background-color:#111111 !important;}
......
......@@ -11,4 +11,5 @@ $theme-colors: ("primary": $blue, "secondary": $black)
@import ../../../node_modules/bootstrap/scss/bootstrap
// Add SASS theme customizations here..
@import ../abstract/_members
@import ./_common
......@@ -24,4 +24,5 @@ $enable-rounded:false;
@import "../../../node_modules/bootstrap/scss/bootstrap";
// Add SASS theme customizations here..
@import "../abstract/_members";
@import "./common";
......@@ -16,4 +16,5 @@ $dark:#072247;
@import "../../../node_modules/bootstrap/scss/bootstrap";
// Add SASS theme customizations here..
@import "../abstract/_members";
@import "./common";
......@@ -22,4 +22,5 @@ $dark:#111111;
@import "../../../node_modules/bootstrap/scss/bootstrap";
// Add SASS theme customizations here..
@import "../abstract/_members";
@import "./common";
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment