From bedfd7e30c4c331628968ea62fef736a1d2cd542 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 10 Oct 2019 10:35:11 -0400 Subject: added manager for managing brush tool state on rich text editor + updated styling to match more with pdf --- src/client/util/TooltipTextMenu.scss | 1006 +++++++++++++++++++++++++--------- src/client/util/TooltipTextMenu.tsx | 566 ++++++++++++------- 2 files changed, 1112 insertions(+), 460 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index ebf833dbe..6bafdbbfd 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -1,100 +1,68 @@ @import "../views/globalCssVariables"; - -.ProseMirror-textblock-dropdown { - min-width: 3em; - } - - .ProseMirror-menu { - margin: 0 -4px; - line-height: 1; - } - - .ProseMirror-tooltip .ProseMirror-menu { - width: -webkit-fit-content; - width: fit-content; - white-space: pre; - } - - .ProseMirror-menuitem { - margin-right: 3px; +.ProseMirror-menu-dropdown-wrap { + // margin: 0 4px; display: inline-block; - z-index: 50000; position: relative; - } - - .ProseMirror-menuseparator { - // border-right: 1px solid #ddd; - margin-right: 3px; - } - - .ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu { - font-size: 90%; - white-space: nowrap; - } +} - .ProseMirror-menu-dropdown { +.ProseMirror-menu-dropdown { vertical-align: 1px; cursor: pointer; position: relative; - padding-right: 15px; - margin: 3px; + padding: 0 15px 0 4px; background: white; - border-radius: 3px; - text-align: center; - } - - .ProseMirror-menu-dropdown-wrap { - padding: 1px 0 1px 4px; - display: inline-block; + border-radius: 2px; + text-align: left; + font-size: 12px; + white-space: nowrap; + margin-right: 4px; + + &:after { + content: ""; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid currentColor; + opacity: .6; + position: absolute; + right: 4px; + top: calc(50% - 2px); + } +} + +.ProseMirror-menu-submenu-wrap { position: relative; - } - - .ProseMirror-menu-dropdown:after { - content: ""; - border-left: 4px solid transparent; - border-right: 4px solid transparent; - border-top: 4px solid currentColor; - opacity: .6; - position: absolute; - right: 4px; - top: calc(50% - 2px); - } - - .ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu { - background: $dark-color; - color:white; + margin-right: -4px; +} + +.ProseMirror-menu-dropdown-menu, +.ProseMirror-menu-submenu { + font-size: 12px; + background: white; border: 1px solid rgb(223, 223, 223); - padding: 2px; - } - - .ProseMirror-menu-dropdown-menu { + min-width: 50px; z-index: 50000; - min-width: 6em; - background: white; position: absolute; - } - - .linking { - text-align: center; - } - .ProseMirror-menu-dropdown-item { - cursor: pointer; - padding: 2px 8px 2px 4px; - width: auto; - z-index: 100000; - } - - .ProseMirror-menu-dropdown-item:hover { - background: white; - } - - .ProseMirror-menu-submenu-wrap { - position: relative; - margin-right: -4px; - } - - .ProseMirror-menu-submenu-label:after { + .ProseMirror-menu-dropdown-item { + cursor: pointer; + z-index: 100000; + text-align: left; + padding: 3px; + + &:hover { + background-color: $light-color-secondary; + } + } +} + +// .ProseMirror-menu-submenu { +// display: none; +// min-width: 4em; +// left: 100%; +// top: -3px; +// } + +.ProseMirror-menu-submenu-label:after { content: ""; border-top: 4px solid transparent; border-bottom: 4px solid transparent; @@ -103,153 +71,144 @@ position: absolute; right: 4px; top: calc(50% - 4px); - } - - .ProseMirror-menu-submenu { - display: none; - min-width: 4em; - left: 100%; - top: -3px; - } - - .ProseMirror-menu-active { - background: #eee; - border-radius: 4px; - } - - .ProseMirror-menu-active { - background: #eee; - border-radius: 4px; - } - - .ProseMirror-menu-disabled { - opacity: .3; - } - - .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu { - display: block; - } +} + + - .ProseMirror-menubar { - border-top-left-radius: inherit; - border-top-right-radius: inherit; - position: relative; - min-height: 1em; - color: white; - padding: 10px 10px; - top: 0; left: 0; right: 0; - border-bottom: 1px solid silver; - background:$dark-color; - z-index: 10; - -moz-box-sizing: border-box; - box-sizing: border-box; - overflow: visible; - } +// .ProseMirror-menu-active { +// background: #eee; +// border-radius: 4px; +// } + +// .ProseMirror-menu-disabled { +// opacity: .3; +// } + + +// .ProseMirror-menubar { +// border-top-left-radius: inherit; +// border-top-right-radius: inherit; +// position: relative; +// min-height: 1em; +// color: white; +// padding: 10px 10px; +// top: 0; left: 0; right: 0; +// border-bottom: 1px solid silver; +// background:$dark-color; +// z-index: 10; +// -moz-box-sizing: border-box; +// box-sizing: border-box; +// overflow: visible; +// } .ProseMirror-icon { display: inline-block; - line-height: .8; - vertical-align: -2px; /* Compensate for padding */ - padding: 2px 8px; + // line-height: .8; + // vertical-align: -2px; /* Compensate for padding */ + // padding: 2px 8px; cursor: pointer; - } - - .ProseMirror-menu-disabled.ProseMirror-icon { - cursor: default; - } - - .ProseMirror-icon svg { - fill:white; - height: 1em; - } - - .ProseMirror-icon span { - vertical-align: text-top; - } - - .ProseMirror ul, .ProseMirror ol { - padding-left: 30px; - } - - .ProseMirror blockquote { - padding-left: 1em; - border-left: 3px solid #eee; - margin-left: 0; margin-right: 0; - } - - .ProseMirror-example-setup-style img { - cursor: default; - } - - .ProseMirror-prompt { - background: white; - padding: 5px 10px 5px 15px; - border: 1px solid silver; - position: fixed; - border-radius: 3px; - z-index: 11; - box-shadow: -.5px 2px 5px white(255, 255, 255, 0.2); - } - - .ProseMirror-prompt h5 { - margin: 0; - font-weight: normal; - font-size: 100%; - color: #444; - } - - .ProseMirror-prompt input[type="text"], - .ProseMirror-prompt textarea { - background: white; - border: none; - outline: none; - } - - .ProseMirror-prompt input[type="text"] { - padding: 0 4px; - } - - .ProseMirror-prompt-close { - position: absolute; - left: 2px; top: 1px; - color: #666; - border: none; background: transparent; padding: 0; - } - - .ProseMirror-prompt-close:after { - content: "✕"; - font-size: 12px; - } - - .ProseMirror-invalid { - background: #ffc; - border: 1px solid #cc7; - border-radius: 4px; - padding: 5px 10px; + + &.ProseMirror-menu-disabled { + cursor: default; + } + + svg { + fill:white; + height: 1em; + } + + span { + vertical-align: text-top; + } + } + +// .ProseMirror ul, .ProseMirror ol { +// padding-left: 30px; +// } + +// .ProseMirror blockquote { +// padding-left: 1em; +// border-left: 3px solid #eee; +// margin-left: 0; margin-right: 0; +// } + +// .ProseMirror-example-setup-style img { +// cursor: default; +// } + +// .ProseMirror-prompt { +// background: white; +// padding: 5px 10px 5px 15px; +// border: 1px solid silver; +// position: fixed; +// border-radius: 3px; +// z-index: 11; +// box-shadow: -.5px 2px 5px white(255, 255, 255, 0.2); +// } + +// .ProseMirror-prompt h5 { +// margin: 0; +// font-weight: normal; +// font-size: 100%; +// color: #444; +// } + +// .ProseMirror-prompt input[type="text"], +// .ProseMirror-prompt textarea { +// background: white; +// border: none; +// outline: none; +// } + +// .ProseMirror-prompt input[type="text"] { +// padding: 0 4px; +// } + +// .ProseMirror-prompt-close { +// position: absolute; +// left: 2px; top: 1px; +// color: #666; +// border: none; background: transparent; padding: 0; +// } + +// .ProseMirror-prompt-close:after { +// content: "✕"; +// font-size: 12px; +// } + +// .ProseMirror-invalid { +// background: #ffc; +// border: 1px solid #cc7; +// border-radius: 4px; +// padding: 5px 10px; +// position: absolute; +// min-width: 10em; +// } + +// .ProseMirror-prompt-buttons { +// margin-top: 5px; +// display: none; +// } + +.wrapper { position: absolute; - min-width: 10em; - } - - .ProseMirror-prompt-buttons { - margin-top: 5px; - display: none; - } + pointer-events: all; +} .tooltipMenu { position: absolute; z-index: 20000; - background: #121721; - border: 1px solid silver; - border-radius: 15px; - //height: 60px; - //padding: 2px 10px; - //margin-top: 100px; - //-webkit-transform: translateX(-50%); - //transform: translateX(-50%); + background: #323232; + border-radius: 6px; transform: translateY(-85px); pointer-events: all; - height: fit-content; - width:550px; + height: 35px; + width: 650px; + padding: 3px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + display: flex; + align-items: center; + .ProseMirror-example-setup-style hr { padding: 2px 10px; border: none; @@ -265,54 +224,96 @@ } } -.tooltipExtras { - position: absolute; - z-index: 20000; - background: #121721; - border: 1px solid silver; - border-radius: 15px; - //height: 60px; - //padding: 2px 10px; - //margin-top: 100px; - //-webkit-transform: translateX(-50%); - //transform: translateX(-50%); - transform: translateY(-115px); - pointer-events: all; +// .tooltipExtras { +// position: absolute; +// z-index: 20000; +// background: #121721; +// border: 1px solid silver; +// border-radius: 15px; +// //height: 60px; +// //padding: 2px 10px; +// //margin-top: 100px; +// //-webkit-transform: translateX(-50%); +// //transform: translateX(-50%); +// transform: translateY(-115px); +// pointer-events: all; +// height: 25px; +// width:fit-content; +// .ProseMirror-example-setup-style hr { +// padding: 2px 10px; +// border: none; +// margin: 1em 0; +// } + +// .ProseMirror-example-setup-style hr:after { +// content: ""; +// display: block; +// height: 1px; +// background-color: silver; +// line-height: 2px; +// } +// } + +#link-drag { + background-color: #323232; +} + +.menuicon { + width: 25px; height: 25px; - width:fit-content; - .ProseMirror-example-setup-style hr { - padding: 2px 10px; - border: none; - margin: 1em 0; + cursor: pointer; + text-align: center; + line-height: 25px; + margin: 0 2px; + border-radius: 3px; + + &:hover { + background-color: black; + + #link-drag { + background-color: black; + } } - - .ProseMirror-example-setup-style hr:after { - content: ""; - display: block; - height: 1px; - background-color: silver; - line-height: 2px; + + &> * { + margin-top: 50%; + margin-left: 50%; + transform: translate(-50%, -50%); } -} -.wrapper { - position: absolute; - pointer-events: all; + svg { + fill: white; + width: 18px; + height: 18px; + } } - .menuicon { - display: inline-block; - border-right: 1px solid white(0, 0, 0, 0.2); - //color: rgb(19, 18, 18); - color: rgb(226, 21, 21); - line-height: 1; - padding: 0px 2px; - margin: 1px; +.menuicon-active { + width: 25px; + height: 25px; cursor: pointer; text-align: center; - min-width: 10px; - - } + line-height: 25px; + margin: 0 2px; + border-radius: 3px; + + &:hover { + background-color: black; + } + + &> * { + margin-top: 50%; + margin-left: 50%; + transform: translate(-50%, -50%); + } + + svg { + fill: greenyellow; + width: 18px; + height: 18px; + } +} + .strong, .heading { font-weight: bold; } .em { font-style: italic; } .underline {text-decoration: underline} @@ -328,8 +329,9 @@ height: 20px; text-align: center; } + - .brush{ +.brush{ display: inline-block; width: 1em; height: 1em; @@ -337,19 +339,487 @@ stroke: currentColor; fill: currentColor; margin-right: 15px; - } +} - .brush-active{ +.brush-active{ display: inline-block; width: 1em; height: 1em; stroke-width: 3; - stroke: greenyellow; + // stroke: greenyellow; fill: greenyellow; margin-right: 15px; - } +} + +.dragger { + color: #eee; + margin-left: 5px; + width: 16px; + height: 22px; + display: inline-block; + cursor: grab; + + .dragger-wrapper { + width: 100%; + height: 100%; + display: flex; + justify-content: space-evenly; + } + + .dragger-line { + width: 2px; + height: 100%; + background-color: black; + } +} + +// .menuicon:hover + .ProseMirror-menu-dropdown-wrap .buttonSettings-dropdown, +// .menuicon-active:hover + .ProseMirror-menu-dropdown-wrap .buttonSettings-dropdown { +// background-color: black; +// } + +.button-dropdown-wrapper { + display: flex; + align-content: center; + + &:hover { + background-color: black; + } +} + +.buttonSettings-dropdown { + + &.ProseMirror-menu-dropdown { + width: 10px; + height: 25px; + margin: 0; + padding: 0 2px; + background-color: #323232; + text-align: center; + + &:after { + border-top: 4px solid white; + right: 2px; + } + + &:hover { + background-color: black; + } + } + + &.ProseMirror-menu-dropdown-menu { + min-width: 150px; + left: -27px; + top: 31px; + background-color: #323232; + color: $light-color-secondary; + border: none; + // border: 1px solid $light-color-secondary; + border-radius: 0 6px 6px 6px; + padding: 3px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + + .ProseMirror-menu-dropdown-item{ + cursor: default; + + &:last-child { + border-bottom: none; + } + + &:hover { + background-color: #323232; + } + + .button-setting, .button-setting-disabled { + padding: 2px; + border-radius: 2px; + } + + .button-setting:hover { + cursor: pointer; + background-color: black; + } + + .separated-button { + border-top: 1px solid $light-color-secondary; + padding-top: 6px; + } + + input { + color: black; + border: none; + border-radius: 1px; + padding: 3px; + } + + button { + padding: 6px; + background-color: #323232; + border: 1px solid black; + border-radius: 1px; + + &:hover { + background-color: black; + } + } + } + + + } +} + +// @import "../views/globalCssVariables"; + +// .ProseMirror-textblock-dropdown { +// min-width: 3em; +// } + +// .ProseMirror-menu { +// margin: 0 -4px; +// line-height: 1; +// } + +// .ProseMirror-tooltip .ProseMirror-menu { +// width: -webkit-fit-content; +// width: fit-content; +// white-space: pre; +// } + +// .ProseMirror-menuitem { +// margin-right: 3px; +// display: inline-block; +// z-index: 50000; +// position: relative; +// } + +// .ProseMirror-menuseparator { +// // border-right: 1px solid #ddd; +// margin-right: 3px; +// } + +// .ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu { +// font-size: 90%; +// white-space: nowrap; +// } + +// .ProseMirror-menu-dropdown { +// vertical-align: 1px; +// cursor: pointer; +// position: relative; +// padding-right: 15px; +// margin: 3px; +// background: white; +// border-radius: 3px; +// text-align: center; +// } + +// .ProseMirror-menu-dropdown-wrap { +// padding: 1px 0 1px 4px; +// display: inline-block; +// position: relative; +// } + +// .ProseMirror-menu-dropdown:after { +// content: ""; +// border-left: 4px solid transparent; +// border-right: 4px solid transparent; +// border-top: 4px solid currentColor; +// opacity: .6; +// position: absolute; +// right: 4px; +// top: calc(50% - 2px); +// } + +// .ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu { +// background: $dark-color; +// color:white; +// border: 1px solid rgb(223, 223, 223); +// padding: 2px; +// } + +// .ProseMirror-menu-dropdown-menu { +// z-index: 50000; +// min-width: 6em; +// background: white; +// position: absolute; +// } + +// .linking { +// text-align: center; +// } + +// .ProseMirror-menu-dropdown-item { +// cursor: pointer; +// padding: 2px 8px 2px 4px; +// width: auto; +// z-index: 100000; +// } + +// .ProseMirror-menu-dropdown-item:hover { +// background: white; +// } + +// .ProseMirror-menu-submenu-wrap { +// position: relative; +// margin-right: -4px; +// } + +// .ProseMirror-menu-submenu-label:after { +// content: ""; +// border-top: 4px solid transparent; +// border-bottom: 4px solid transparent; +// border-left: 4px solid currentColor; +// opacity: .6; +// position: absolute; +// right: 4px; +// top: calc(50% - 4px); +// } + +// .ProseMirror-menu-submenu { +// display: none; +// min-width: 4em; +// left: 100%; +// top: -3px; +// } + +// .ProseMirror-menu-active { +// background: #eee; +// border-radius: 4px; +// } + +// .ProseMirror-menu-active { +// background: #eee; +// border-radius: 4px; +// } + +// .ProseMirror-menu-disabled { +// opacity: .3; +// } + +// .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu { +// display: block; +// } + +// .ProseMirror-menubar { +// border-top-left-radius: inherit; +// border-top-right-radius: inherit; +// position: relative; +// min-height: 1em; +// color: white; +// padding: 10px 10px; +// top: 0; left: 0; right: 0; +// border-bottom: 1px solid silver; +// background:$dark-color; +// z-index: 10; +// -moz-box-sizing: border-box; +// box-sizing: border-box; +// overflow: visible; +// } + +// .ProseMirror-icon { +// display: inline-block; +// line-height: .8; +// vertical-align: -2px; /* Compensate for padding */ +// padding: 2px 8px; +// cursor: pointer; +// } + +// .ProseMirror-menu-disabled.ProseMirror-icon { +// cursor: default; +// } + +// .ProseMirror-icon svg { +// fill:white; +// height: 1em; +// } + +// .ProseMirror-icon span { +// vertical-align: text-top; +// } + +// .ProseMirror ul, .ProseMirror ol { +// padding-left: 30px; +// } + +// .ProseMirror blockquote { +// padding-left: 1em; +// border-left: 3px solid #eee; +// margin-left: 0; margin-right: 0; +// } + +// .ProseMirror-example-setup-style img { +// cursor: default; +// } + +// .ProseMirror-prompt { +// background: white; +// padding: 5px 10px 5px 15px; +// border: 1px solid silver; +// position: fixed; +// border-radius: 3px; +// z-index: 11; +// box-shadow: -.5px 2px 5px white(255, 255, 255, 0.2); +// } + +// .ProseMirror-prompt h5 { +// margin: 0; +// font-weight: normal; +// font-size: 100%; +// color: #444; +// } + +// .ProseMirror-prompt input[type="text"], +// .ProseMirror-prompt textarea { +// background: white; +// border: none; +// outline: none; +// } + +// .ProseMirror-prompt input[type="text"] { +// padding: 0 4px; +// } + +// .ProseMirror-prompt-close { +// position: absolute; +// left: 2px; top: 1px; +// color: #666; +// border: none; background: transparent; padding: 0; +// } + +// .ProseMirror-prompt-close:after { +// content: "✕"; +// font-size: 12px; +// } + +// .ProseMirror-invalid { +// background: #ffc; +// border: 1px solid #cc7; +// border-radius: 4px; +// padding: 5px 10px; +// position: absolute; +// min-width: 10em; +// } + +// .ProseMirror-prompt-buttons { +// margin-top: 5px; +// display: none; +// } + +// .tooltipMenu { +// position: absolute; +// z-index: 20000; +// background: #121721; +// border: 1px solid silver; +// border-radius: 15px; +// //height: 60px; +// //padding: 2px 10px; +// //margin-top: 100px; +// //-webkit-transform: translateX(-50%); +// //transform: translateX(-50%); +// transform: translateY(-85px); +// pointer-events: all; +// height: fit-content; +// width:550px; +// .ProseMirror-example-setup-style hr { +// padding: 2px 10px; +// border: none; +// margin: 1em 0; +// } + +// .ProseMirror-example-setup-style hr:after { +// content: ""; +// display: block; +// height: 1px; +// background-color: silver; +// line-height: 2px; +// } +// } + +// .tooltipExtras { +// position: absolute; +// z-index: 20000; +// background: #121721; +// border: 1px solid silver; +// border-radius: 15px; +// //height: 60px; +// //padding: 2px 10px; +// //margin-top: 100px; +// //-webkit-transform: translateX(-50%); +// //transform: translateX(-50%); +// transform: translateY(-115px); +// pointer-events: all; +// height: 25px; +// width:fit-content; +// .ProseMirror-example-setup-style hr { +// padding: 2px 10px; +// border: none; +// margin: 1em 0; +// } + +// .ProseMirror-example-setup-style hr:after { +// content: ""; +// display: block; +// height: 1px; +// background-color: silver; +// line-height: 2px; +// } +// } + +// .wrapper { +// position: absolute; +// pointer-events: all; +// } + +// .menuicon { +// display: inline-block; +// border-right: 1px solid white(0, 0, 0, 0.2); +// //color: rgb(19, 18, 18); +// color: rgb(226, 21, 21); +// line-height: 1; +// padding: 0px 2px; +// margin: 1px; +// cursor: pointer; +// text-align: center; +// min-width: 10px; + +// } +// .strong, .heading { font-weight: bold; } +// .em { font-style: italic; } +// .underline {text-decoration: underline} +// .superscript {vertical-align:super} +// .subscript { vertical-align:sub } +// .strikethrough {text-decoration-line:line-through} +// .font-size-indicator { +// font-size: 12px; +// padding-right: 0px; +// } +// .summarize{ +// color: white; +// height: 20px; +// text-align: center; +// } + +// .brush{ +// display: inline-block; +// width: 1em; +// height: 1em; +// stroke-width: 0; +// stroke: currentColor; +// fill: currentColor; +// margin-right: 15px; +// } + +// .brush-active{ +// display: inline-block; +// width: 1em; +// height: 1em; +// stroke-width: 3; +// stroke: greenyellow; +// fill: greenyellow; +// margin-right: 15px; +// } - .dragger{ - color: #eee; - margin-left: 5px; - } \ No newline at end of file +// .dragger{ +// color: #eee; +// margin-left: 5px; +// } \ No newline at end of file diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index c82d3bc63..6070d2ef4 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -17,79 +17,99 @@ import { DragManager } from "./DragManager"; import { LinkManager } from "./LinkManager"; import { schema } from "./RichTextSchema"; import "./TooltipTextMenu.scss"; -import { Cast, NumCast } from '../../new_fields/Types'; +import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { updateBullets } from './ProsemirrorExampleTransfer'; import { DocumentDecorations } from '../views/DocumentDecorations'; +import { SelectionManager } from './SelectionManager'; const { toggleMark, setBlockType } = require("prosemirror-commands"); const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc. export class TooltipTextMenu { - public tooltip: HTMLElement; + // editor state private view: EditorView; private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; - private fontStyles: MarkType[]; - private fontSizes: MarkType[]; - private listTypes: (NodeType | any)[]; - private fontSizeToNum: Map; - private fontStylesToName: Map; - private listTypeToIcon: Map; + private fontStyles: MarkType[] = []; + private fontSizes: MarkType[] = []; + private listTypes: (NodeType | any)[] = []; + private fontSizeToNum: Map = new Map(); + private fontStylesToName: Map = new Map(); + private listTypeToIcon: Map = new Map(); + private _activeMarks: Mark[] = []; + private _marksToDoms: Map = new Map(); + // private _brushMarks?: Set; //private link: HTMLAnchorElement; - private wrapper: HTMLDivElement; - private extras: HTMLDivElement; + + // editor doms + public tooltip: HTMLElement = document.createElement("div"); + private wrapper: HTMLDivElement = document.createElement("div"); private linkEditor?: HTMLDivElement; private linkText?: HTMLDivElement; private linkDrag?: HTMLImageElement; - //dropdown doms + private _linkDropdownDom?: Node; + private _brushdom?: Node; + private fontSizeDom?: Node; private fontStyleDom?: Node; private listTypeBtnDom?: Node; - private _activeMarks: Mark[] = []; - - private _collapseBtn?: MenuItem; + private _collapsed: boolean = false; + // private _collapseBtn?: MenuItem; + // private _brushIsEmpty: boolean = true; - private _brushMarks?: Set; - private _brushIsEmpty: boolean = true; - private _brushdom?: Node; - private _marksToDoms: Map = new Map(); + public static Toolbar: HTMLDivElement | undefined; - private _collapsed: boolean = false; constructor(view: EditorView) { this.view = view; - this.wrapper = document.createElement("div"); - this.tooltip = document.createElement("div"); - this.extras = document.createElement("div"); + // replace old active menu with this + if (TooltipTextMenuManager.Instance.activeMenu) { + TooltipTextMenuManager.Instance.activeMenu.wrapper.remove(); + } + TooltipTextMenuManager.Instance.activeMenu = this; - this.wrapper.appendChild(this.extras); - this.wrapper.appendChild(this.tooltip); + // initialize the tooltip + this.createTooltip(view); - this.tooltip.className = "tooltipMenu"; - this.extras.className = "tooltipExtras"; + // initialize the wrapper + this.wrapper = document.createElement("div"); this.wrapper.className = "wrapper"; + this.wrapper.appendChild(this.tooltip); - const dragger = document.createElement("span"); - dragger.className = "dragger"; - dragger.textContent = ">>>"; - this.extras.appendChild(dragger); - - this.dragElement(dragger); + // positioning? + TooltipTextMenu.Toolbar = this.wrapper; - // this.createCollapse(); - // if (this._collapseBtn) { - // this.tooltip.appendChild(this._collapseBtn.render(this.view).dom); + // // position wrapper + // if (TooltipTextMenuManager.Instance.isPinned) { + // this.wrapper.style.position = "absolute"; + // this.wrapper.style.left = TooltipTextMenuManager.Instance.pinnedX + "px"; + // this.wrapper.style.top = TooltipTextMenuManager.Instance.pinnedY + "px"; + + // // if pinned, append to mainview + // const mainView = document.querySelector("#main-div"); + // mainView && mainView.appendChild(this.wrapper); + // } else { + // this.wrapper.style.position = "absolute"; + // this.wrapper.style.top = (this.wrapper.offsetTop + TooltipTextMenuManager.Instance.unpinnedY) + "px"; + // this.wrapper.style.left = (this.wrapper.offsetLeft + TooltipTextMenuManager.Instance.unpinnedX) + "px"; + + // // add tooltip to outerdiv to circumvent scaling problem + // const outer_div = this.editorProps.outer_div; + // outer_div && outer_div(this.wrapper); // } - //add the div which is the tooltip - //view.dom.parentNode!.parentNode!.appendChild(this.tooltip); - //add additional icons - library.add(faListUl); - //add the buttons to the tooltip + } + + private async createTooltip(view: EditorView) { + // initialize element + this.tooltip = document.createElement("div"); + this.tooltip.className = "tooltipMenu"; + + // init buttons to the tooltip let items = [ { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong", "Bold") }, { command: toggleMark(schema.marks.em), dom: this.icon("i", "em", "Italic") }, @@ -100,8 +120,8 @@ export class TooltipTextMenu { { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') } ]; + // add menu items this._marksToDoms = new Map(); - //add menu items items.forEach(({ dom, command }) => { this.tooltip.appendChild(dom); switch (dom.title) { @@ -131,10 +151,46 @@ export class TooltipTextMenu { }); }); + + // link menu this.updateLinkMenu(); + let dropdown = await this.createLinkDropdown(); + let newLinkDropdowndom = dropdown.render(this.view).dom; + this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); + this._linkDropdownDom = newLinkDropdowndom; + + // list of font styles + this.initFontStyles(); + + // font sizes + this.initFontSizes(); + + // list types + this.initListTypes(); + + // init brush tool + this._brushdom = this.createBrush().render(this.view).dom; + this.tooltip.appendChild(this._brushdom); + // this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom; + // this.tooltip.appendChild(this._brushDropdownDom); + + // star and pin + this.tooltip.appendChild(this.createLink().render(this.view).dom); + this.tooltip.appendChild(this.createStar().render(this.view).dom); + // + this.updateListItemDropdown(":", this.listTypeBtnDom); + + // + this.updateFromDash(view, undefined, undefined); + // TooltipTextMenu.Toolbar = this.wrapper; - //list of font styles + // dragger + // TODO: onclick handler in drag handles collapsing + this.createDragger(); + } + + initFontStyles() { this.fontStylesToName = new Map(); this.fontStylesToName.set(schema.marks.timesNewRoman, "Times New Roman"); this.fontStylesToName.set(schema.marks.arial, "Arial"); @@ -144,8 +200,9 @@ export class TooltipTextMenu { this.fontStylesToName.set(schema.marks.impact, "Impact"); this.fontStylesToName.set(schema.marks.crimson, "Crimson Text"); this.fontStyles = Array.from(this.fontStylesToName.keys()); + } - //font sizes + initFontSizes() { this.fontSizeToNum = new Map(); this.fontSizeToNum.set(schema.marks.p10, 10); this.fontSizeToNum.set(schema.marks.p12, 12); @@ -158,39 +215,41 @@ export class TooltipTextMenu { this.fontSizeToNum.set(schema.marks.p48, 48); this.fontSizeToNum.set(schema.marks.p72, 72); this.fontSizeToNum.set(schema.marks.pFontSize, 10); - // this.fontSizeToNum.set(schema.marks.pFontSize, 12); - // this.fontSizeToNum.set(schema.marks.pFontSize, 14); - // this.fontSizeToNum.set(schema.marks.pFontSize, 16); - // this.fontSizeToNum.set(schema.marks.pFontSize, 18); - // this.fontSizeToNum.set(schema.marks.pFontSize, 20); - // this.fontSizeToNum.set(schema.marks.pFontSize, 24); - // this.fontSizeToNum.set(schema.marks.pFontSize, 32); - // this.fontSizeToNum.set(schema.marks.pFontSize, 48); - // this.fontSizeToNum.set(schema.marks.pFontSize, 72); this.fontSizes = Array.from(this.fontSizeToNum.keys()); + } - //list types + initListTypes() { this.listTypeToIcon = new Map(); this.listTypeToIcon.set(schema.nodes.bullet_list, ":"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A"); // this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜"); this.listTypes = Array.from(this.listTypeToIcon.keys()); + } - //custom tools - // this.tooltip.appendChild(this.createLink().render(this.view).dom); + createDragger() { + const dragger = document.createElement("div"); + dragger.className = "dragger"; - this._brushdom = this.createBrush().render(this.view).dom; - this.tooltip.appendChild(this._brushdom); - this.tooltip.appendChild(this.createLink().render(this.view).dom); - this.tooltip.appendChild(this.createStar().render(this.view).dom); + let draggerWrapper = document.createElement("div"); + draggerWrapper.className = "dragger-wrapper"; - this.updateListItemDropdown(":", this.listTypeBtnDom); + let line1 = document.createElement("span"); + line1.className = "dragger-line"; + let line2 = document.createElement("span"); + line2.className = "dragger-line"; + let line3 = document.createElement("span"); + line3.className = "dragger-line"; - this.updateFromDash(view, undefined, undefined); - TooltipTextMenu.Toolbar = this.wrapper; + draggerWrapper.appendChild(line1); + draggerWrapper.appendChild(line2); + draggerWrapper.appendChild(line3); + + dragger.appendChild(draggerWrapper); + + this.tooltip.appendChild(dragger); + this.dragElement(dragger); } - public static Toolbar: HTMLDivElement | undefined; //label of dropdown will change to given label updateFontSizeDropdown(label: string) { @@ -243,24 +302,13 @@ export class TooltipTextMenu { if (!this.linkEditor || !this.linkText) { this.linkEditor = document.createElement("div"); this.linkEditor.className = "ProseMirror-icon menuicon"; - this.linkEditor.style.color = "black"; - this.linkText = document.createElement("div"); - this.linkText.style.cssFloat = "left"; - this.linkText.style.marginRight = "5px"; - this.linkText.style.marginLeft = "5px"; - this.linkText.setAttribute("contenteditable", "true"); - this.linkText.style.whiteSpace = "nowrap"; - this.linkText.style.width = "150px"; - this.linkText.style.overflow = "hidden"; - this.linkText.style.color = "white"; - this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }; - let linkBtn = document.createElement("div"); - linkBtn.textContent = ">>"; - linkBtn.style.width = "10px"; - linkBtn.style.height = "10px"; - linkBtn.style.color = "white"; - linkBtn.style.cssFloat = "left"; - linkBtn.onpointerdown = (e: PointerEvent) => { + this.linkDrag = document.createElement("img"); + this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; + this.linkDrag.style.width = "13px"; + this.linkDrag.style.height = "13px"; + this.linkDrag.title = "Drag to create link"; + this.linkDrag.id = "link-drag"; + this.linkDrag.onpointerdown = (e: PointerEvent) => { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); if (link) { @@ -281,62 +329,137 @@ export class TooltipTextMenu { e.preventDefault(); } }; - this.linkDrag = document.createElement("img"); - this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; - this.linkDrag.style.width = "15px"; - this.linkDrag.style.height = "15px"; - this.linkDrag.title = "Drag to create link"; - this.linkDrag.style.color = "black"; - this.linkDrag.style.background = "black"; - this.linkDrag.style.cssFloat = "left"; - this.linkDrag.onpointerdown = (e: PointerEvent) => { - if (!this.editorProps) return; - let dragData = new DragManager.LinkDragData(this.editorProps.Document); - dragData.dontClearTextBox = true; - // hack to get source context -sy - let docView = DocumentManager.Instance.getDocumentView(this.editorProps.Document); - e.stopPropagation(); - let ctrlKey = e.ctrlKey; - DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY, - { - handlers: { - dragComplete: action(() => { - if (dragData.linkDocument) { - let linkDoc = dragData.linkDocument; - let proto = Doc.GetProto(linkDoc); - if (proto && docView) { - proto.sourceContext = docView.props.ContainingCollectionDoc; - } - let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab"); - if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) { - proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link - } - } - }), - }, - hideSource: false - }); - e.stopPropagation(); - e.preventDefault(); - }; this.linkEditor.appendChild(this.linkDrag); this.tooltip.appendChild(this.linkEditor); } + // let node = this.view.state.selection.$from.nodeAfter; + // let link = node && node.marks.find(m => m.type.name === "link"); + // this.linkText.textContent = link ? link.attrs.href : "-empty-"; + + // this.linkText.onkeydown = (e: KeyboardEvent) => { + // if (e.key === "Enter") { + // // this.makeLink(this.linkText!.textContent!); + // e.stopPropagation(); + // e.preventDefault(); + // } + // }; + // // this.tooltip.appendChild(this.linkEditor); + } + + async getTextLinkTargetTitle() { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); - this.linkText.textContent = link ? link.attrs.href : "-empty-"; + if (link) { + let href = link.attrs.href; + if (href) { + if (href.indexOf(Utils.prepend("/doc/")) === 0) { + const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + if (linkclicked) { + let linkDoc = await DocServer.GetRefField(linkclicked); + if (linkDoc instanceof Doc) { + let anchor1 = await Cast(linkDoc.anchor1, Doc); + let anchor2 = await Cast(linkDoc.anchor2, Doc); + let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; + if (currentDoc && anchor1 && anchor2) { + if (Doc.AreProtosEqual(currentDoc, anchor1)) return StrCast(anchor2.title); + if (Doc.AreProtosEqual(currentDoc, anchor2)) return StrCast(anchor2.title); + } + } + } + } else { + return href; + } + } + } + } - this.linkText.onkeydown = (e: KeyboardEvent) => { - if (e.key === "Enter") { - // this.makeLink(this.linkText!.textContent!); - e.stopPropagation(); - e.preventDefault(); + async createLinkDropdown() { + let targetTitle = await this.getTextLinkTargetTitle(); + let input = document.createElement("input"); + + // menu item for input for hyperlink url + // TODO: integrate search to allow users to search for a doc to link to + let linkInfo = new MenuItem({ + title: "", + execEvent: "", + class: "button-setting-disabled", + css: "", + render() { + let p = document.createElement("p"); + p.textContent = "Linked to:"; + + input.type = "text"; + input.placeholder = "Enter URL"; + if (targetTitle) input.value = targetTitle; + input.onclick = (e: MouseEvent) => { + input.select(); + input.focus(); + }; + + let div = document.createElement("div"); + div.appendChild(p); + div.appendChild(input); + return div; + }, + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); } - }; - // this.tooltip.appendChild(this.linkEditor); + }); + + // menu item to update/apply the hyperlink to the selected text + let linkApply = new MenuItem({ + title: "", + execEvent: "", + class: "", + css: "", + render() { + let button = document.createElement("button"); + button.className = "link-url-button"; + button.textContent = "Apply hyperlink"; + return button; + }, + enable() { return false; }, + run: (state, dispatch, view, event) => { + event.stopPropagation(); + this.makeLink(input.value, "onRight"); + } + }); + + // menu item to remove the link + // TODO: allow this to be undoable + let self = this; + let deleteLink = new MenuItem({ + title: "Delete link", + execEvent: "", + class: "separated-button", + css: "", + render() { + let button = document.createElement("button"); + button.textContent = "Remove link"; + + let wrapper = document.createElement("div"); + wrapper.appendChild(button); + return wrapper; + }, + enable() { return true; }, + async run() { + self.deleteLink(); + // update link dropdown + let dropdown = await self.createLinkDropdown(); + let newLinkDropdowndom = dropdown.render(self.view).dom; + self._linkDropdownDom && self.tooltip.replaceChild(newLinkDropdowndom, self._linkDropdownDom); + self._linkDropdownDom = newLinkDropdowndom; + } + }); + + + let linkDropdown = new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem; + return linkDropdown; } + dragElement(elmnt: HTMLElement) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; if (elmnt) { @@ -627,15 +750,15 @@ export class TooltipTextMenu { // selectionchanged event handler brush_function(state: EditorState, dispatch: any) { - if (this._brushIsEmpty) { + if (TooltipTextMenuManager.Instance._brushIsEmpty) { const selected_marks = this.getMarksInSelection(this.view.state); if (this._brushdom) { if (selected_marks.size >= 0) { - this._brushMarks = selected_marks; + TooltipTextMenuManager.Instance._brushMarks = selected_marks; const newbrush = this.createBrush(true).render(this.view).dom; this.tooltip.replaceChild(newbrush, this._brushdom); this._brushdom = newbrush; - this._brushIsEmpty = !this._brushIsEmpty; + TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty; } } } @@ -643,9 +766,9 @@ export class TooltipTextMenu { let { from, to, $from } = this.view.state.selection; if (this._brushdom) { if (!this.view.state.selection.empty && $from && $from.nodeAfter) { - if (this._brushMarks && to - from > 0) { + if (TooltipTextMenuManager.Instance._brushMarks && to - from > 0) { this.view.dispatch(this.view.state.tr.removeMark(from, to)); - Array.from(this._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { + Array.from(TooltipTextMenuManager.Instance._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { const markType = mark.type; this.changeToMarkInGroup(markType, this.view, []); }); @@ -655,7 +778,7 @@ export class TooltipTextMenu { const newbrush = this.createBrush(false).render(this.view).dom; this.tooltip.replaceChild(newbrush, this._brushdom); this._brushdom = newbrush; - this._brushIsEmpty = !this._brushIsEmpty; + TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty; } } } @@ -663,46 +786,46 @@ export class TooltipTextMenu { } - createCollapse() { - this._collapseBtn = new MenuItem({ - title: "Collapse", - //label: "Collapse", - icon: icons.join, - execEvent: "", - css: "color:white;", - class: "summarize", - run: () => { - this.collapseToolTip(); - } - }); - } + // createCollapse() { + // this._collapseBtn = new MenuItem({ + // title: "Collapse", + // //label: "Collapse", + // icon: icons.join, + // execEvent: "", + // css: "color:white;", + // class: "summarize", + // run: () => { + // this.collapseToolTip(); + // } + // }); + // } - collapseToolTip() { - if (this._collapseBtn) { - if (this._collapseBtn.spec.title === "Collapse") { - // const newcollapseBtn = new MenuItem({ - // title: "Expand", - // icon: icons.join, - // execEvent: "", - // css: "color:white;", - // class: "summarize", - // run: (state, dispatch, view) => { - // this.collapseToolTip(); - // } - // }); - // this.tooltip.replaceChild(newcollapseBtn.render(this.view).dom, this._collapseBtn.render(this.view).dom); - // this._collapseBtn = newcollapseBtn; - this.tooltip.style.width = "30px"; - this._collapseBtn.spec.title = "Expand"; - this._collapseBtn.render(this.view); - } - else { - this._collapseBtn.spec.title = "Collapse"; - this.tooltip.style.width = "550px"; - this._collapseBtn.render(this.view); - } - } - } + // collapseToolTip() { + // if (this._collapseBtn) { + // if (this._collapseBtn.spec.title === "Collapse") { + // // const newcollapseBtn = new MenuItem({ + // // title: "Expand", + // // icon: icons.join, + // // execEvent: "", + // // css: "color:white;", + // // class: "summarize", + // // run: (state, dispatch, view) => { + // // this.collapseToolTip(); + // // } + // // }); + // // this.tooltip.replaceChild(newcollapseBtn.render(this.view).dom, this._collapseBtn.render(this.view).dom); + // // this._collapseBtn = newcollapseBtn; + // this.tooltip.style.width = "30px"; + // this._collapseBtn.spec.title = "Expand"; + // this._collapseBtn.render(this.view); + // } + // else { + // this._collapseBtn.spec.title = "Collapse"; + // this.tooltip.style.width = "550px"; + // this._collapseBtn.render(this.view); + // } + // } + // } createLink() { let markType = schema.marks.link; @@ -913,32 +1036,41 @@ export class TooltipTextMenu { update_mark_doms() { this.reset_mark_doms(); let foundlink = false; - let children = this.extras.childNodes; + // let children = this.extras.childNodes; this._activeMarks.forEach((mark) => { if (this._marksToDoms.has(mark)) { let dom = this._marksToDoms.get(mark); if (dom) dom.style.color = "greenyellow"; } - if (children.length > 1) { - foundlink = true; - } - if (mark.type.name === "link" && children.length === 1) { - // let del = document.createElement("button"); - // del.textContent = "X"; - // del.style.color = "red"; - // del.style.height = "10px"; - // del.style.width = "10px"; - // del.style.marginLeft = "5px"; - // del.onclick = this.deleteLink; - // this.extras.appendChild(del); - let del = this.deleteLinkItem().render(this.view).dom; - this.extras.appendChild(del); - foundlink = true; - } + // if (children.length > 1) { + // foundlink = true; + // } + // if (mark.type.name === "link" && children.length === 1) { + // // let del = document.createElement("button"); + // // del.textContent = "X"; + // // del.style.color = "red"; + // // del.style.height = "10px"; + // // del.style.width = "10px"; + // // del.style.marginLeft = "5px"; + // // del.onclick = this.deleteLink; + // // this.extras.appendChild(del); + // let del = this.deleteLinkItem().render(this.view).dom; + // this.extras.appendChild(del); + // foundlink = true; + // } }); - if (!foundlink) { - if (children.length > 1) { - this.extras.removeChild(children[1]); + // if (!foundlink) { + // if (children.length > 1) { + // this.extras.removeChild(children[1]); + // } + // } + + // keeps brush tool highlighted if active when switching between textboxes + if (!TooltipTextMenuManager.Instance._brushIsEmpty) { + if (this._brushdom) { + const newbrush = this.createBrush(true).render(this.view).dom; + this.tooltip.replaceChild(newbrush, this._brushdom); + this._brushdom = newbrush; } } @@ -1022,3 +1154,53 @@ export class TooltipTextMenu { this.wrapper.remove(); } } + + +class TooltipTextMenuManager { + private static _instance: TooltipTextMenuManager; + + public pinnedX: number = 0; + public pinnedY: number = 0; + public unpinnedX: number = 0; + public unpinnedY: number = 0; + private _isPinned: boolean = false; + + public _brushMarks: Set | undefined; + public _brushIsEmpty: boolean = true; + + public activeMenu: TooltipTextMenu | undefined; + + static get Instance() { + if (!TooltipTextMenuManager._instance) { + TooltipTextMenuManager._instance = new TooltipTextMenuManager(); + } + return TooltipTextMenuManager._instance; + } + + // private pinnedToUnpinned() { + // let position = MainOverlayTextBox.Instance.position; + + // this.unpinnedX = this.pinnedX - position[0]; + // this.unpinnedY = this.pinnedY - position[1]; + // } + + // private unpinnedToPinned() { + // let position = MainOverlayTextBox.Instance.position; + + // this.pinnedX = position[0] + this.unpinnedX; + // this.pinnedY = position[1] + this.unpinnedY; + // } + + public get isPinned() { + return this._isPinned; + } + + public toggleIsPinned() { + // if (this._isPinned) { + // this.pinnedToUnpinned(); + // } else { + // this.unpinnedToPinned(); + // } + this._isPinned = !this._isPinned; + } +} -- cgit v1.2.3-70-g09d2 From dbf41b6eaaebe16567289ba4814316b2c79e0b65 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 10 Oct 2019 10:50:53 -0400 Subject: richtexteditor menuicon styling --- src/client/util/TooltipTextMenu.scss | 132 ------------- src/client/util/TooltipTextMenu.tsx | 272 ++++++++++++++------------- src/client/views/nodes/FormattedTextBox.scss | 22 +-- 3 files changed, 153 insertions(+), 273 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 6bafdbbfd..11c937471 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -55,12 +55,6 @@ } } -// .ProseMirror-menu-submenu { -// display: none; -// min-width: 4em; -// left: 100%; -// top: -3px; -// } .ProseMirror-menu-submenu-label:after { content: ""; @@ -72,34 +66,6 @@ right: 4px; top: calc(50% - 4px); } - - - -// .ProseMirror-menu-active { -// background: #eee; -// border-radius: 4px; -// } - -// .ProseMirror-menu-disabled { -// opacity: .3; -// } - - -// .ProseMirror-menubar { -// border-top-left-radius: inherit; -// border-top-right-radius: inherit; -// position: relative; -// min-height: 1em; -// color: white; -// padding: 10px 10px; -// top: 0; left: 0; right: 0; -// border-bottom: 1px solid silver; -// background:$dark-color; -// z-index: 10; -// -moz-box-sizing: border-box; -// box-sizing: border-box; -// overflow: visible; -// } .ProseMirror-icon { display: inline-block; @@ -121,74 +87,6 @@ vertical-align: text-top; } } - -// .ProseMirror ul, .ProseMirror ol { -// padding-left: 30px; -// } - -// .ProseMirror blockquote { -// padding-left: 1em; -// border-left: 3px solid #eee; -// margin-left: 0; margin-right: 0; -// } - -// .ProseMirror-example-setup-style img { -// cursor: default; -// } - -// .ProseMirror-prompt { -// background: white; -// padding: 5px 10px 5px 15px; -// border: 1px solid silver; -// position: fixed; -// border-radius: 3px; -// z-index: 11; -// box-shadow: -.5px 2px 5px white(255, 255, 255, 0.2); -// } - -// .ProseMirror-prompt h5 { -// margin: 0; -// font-weight: normal; -// font-size: 100%; -// color: #444; -// } - -// .ProseMirror-prompt input[type="text"], -// .ProseMirror-prompt textarea { -// background: white; -// border: none; -// outline: none; -// } - -// .ProseMirror-prompt input[type="text"] { -// padding: 0 4px; -// } - -// .ProseMirror-prompt-close { -// position: absolute; -// left: 2px; top: 1px; -// color: #666; -// border: none; background: transparent; padding: 0; -// } - -// .ProseMirror-prompt-close:after { -// content: "✕"; -// font-size: 12px; -// } - -// .ProseMirror-invalid { -// background: #ffc; -// border: 1px solid #cc7; -// border-radius: 4px; -// padding: 5px 10px; -// position: absolute; -// min-width: 10em; -// } - -// .ProseMirror-prompt-buttons { -// margin-top: 5px; -// display: none; -// } .wrapper { position: absolute; @@ -224,36 +122,6 @@ } } -// .tooltipExtras { -// position: absolute; -// z-index: 20000; -// background: #121721; -// border: 1px solid silver; -// border-radius: 15px; -// //height: 60px; -// //padding: 2px 10px; -// //margin-top: 100px; -// //-webkit-transform: translateX(-50%); -// //transform: translateX(-50%); -// transform: translateY(-115px); -// pointer-events: all; -// height: 25px; -// width:fit-content; -// .ProseMirror-example-setup-style hr { -// padding: 2px 10px; -// border: none; -// margin: 1em 0; -// } - -// .ProseMirror-example-setup-style hr:after { -// content: ""; -// display: block; -// height: 1px; -// background-color: silver; -// line-height: 2px; -// } -// } - #link-drag { background-color: #323232; } diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 6070d2ef4..d0bb753ba 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -154,10 +154,10 @@ export class TooltipTextMenu { // link menu this.updateLinkMenu(); - let dropdown = await this.createLinkDropdown(); - let newLinkDropdowndom = dropdown.render(this.view).dom; - this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); - this._linkDropdownDom = newLinkDropdowndom; + // let dropdown = await this.createLinkDropdown(); + // let newLinkDropdowndom = dropdown.render(this.view).dom; + // this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); + // this._linkDropdownDom = newLinkDropdowndom; // list of font styles this.initFontStyles(); @@ -302,13 +302,24 @@ export class TooltipTextMenu { if (!this.linkEditor || !this.linkText) { this.linkEditor = document.createElement("div"); this.linkEditor.className = "ProseMirror-icon menuicon"; - this.linkDrag = document.createElement("img"); - this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; - this.linkDrag.style.width = "13px"; - this.linkDrag.style.height = "13px"; - this.linkDrag.title = "Drag to create link"; - this.linkDrag.id = "link-drag"; - this.linkDrag.onpointerdown = (e: PointerEvent) => { + this.linkEditor.style.color = "black"; + this.linkText = document.createElement("div"); + this.linkText.style.cssFloat = "left"; + this.linkText.style.marginRight = "5px"; + this.linkText.style.marginLeft = "5px"; + this.linkText.setAttribute("contenteditable", "true"); + this.linkText.style.whiteSpace = "nowrap"; + this.linkText.style.width = "150px"; + this.linkText.style.overflow = "hidden"; + this.linkText.style.color = "white"; + this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }; + let linkBtn = document.createElement("div"); + linkBtn.textContent = ">>"; + linkBtn.style.width = "10px"; + linkBtn.style.height = "10px"; + linkBtn.style.color = "white"; + linkBtn.style.cssFloat = "left"; + linkBtn.onpointerdown = (e: PointerEvent) => { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); if (link) { @@ -329,136 +340,137 @@ export class TooltipTextMenu { e.preventDefault(); } }; + this.linkDrag = document.createElement("img"); + this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; + this.linkDrag.style.width = "15px"; + this.linkDrag.style.height = "15px"; + this.linkDrag.title = "Drag to create link"; + this.linkDrag.style.color = "black"; + this.linkDrag.style.background = "black"; + this.linkDrag.style.cssFloat = "left"; + this.linkDrag.onpointerdown = (e: PointerEvent) => { + if (!this.editorProps) return; + let dragData = new DragManager.LinkDragData(this.editorProps.Document); + dragData.dontClearTextBox = true; + // hack to get source context -sy + let docView = DocumentManager.Instance.getDocumentView(this.editorProps.Document); + e.stopPropagation(); + let ctrlKey = e.ctrlKey; + DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY, + { + handlers: { + dragComplete: action(() => { + if (dragData.linkDocument) { + let linkDoc = dragData.linkDocument; + let proto = Doc.GetProto(linkDoc); + if (proto && docView) { + proto.sourceContext = docView.props.ContainingCollectionDoc; + } + let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab"); + if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) { + proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link + } + } + }), + }, + hideSource: false + }); + e.stopPropagation(); + e.preventDefault(); + }; this.linkEditor.appendChild(this.linkDrag); this.tooltip.appendChild(this.linkEditor); } - // let node = this.view.state.selection.$from.nodeAfter; - // let link = node && node.marks.find(m => m.type.name === "link"); - // this.linkText.textContent = link ? link.attrs.href : "-empty-"; - - // this.linkText.onkeydown = (e: KeyboardEvent) => { - // if (e.key === "Enter") { - // // this.makeLink(this.linkText!.textContent!); - // e.stopPropagation(); - // e.preventDefault(); - // } - // }; - // // this.tooltip.appendChild(this.linkEditor); - } - - async getTextLinkTargetTitle() { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); - if (link) { - let href = link.attrs.href; - if (href) { - if (href.indexOf(Utils.prepend("/doc/")) === 0) { - const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - if (linkclicked) { - let linkDoc = await DocServer.GetRefField(linkclicked); - if (linkDoc instanceof Doc) { - let anchor1 = await Cast(linkDoc.anchor1, Doc); - let anchor2 = await Cast(linkDoc.anchor2, Doc); - let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; - if (currentDoc && anchor1 && anchor2) { - if (Doc.AreProtosEqual(currentDoc, anchor1)) return StrCast(anchor2.title); - if (Doc.AreProtosEqual(currentDoc, anchor2)) return StrCast(anchor2.title); - } - } - } - } else { - return href; - } - } - } - } - - async createLinkDropdown() { - let targetTitle = await this.getTextLinkTargetTitle(); - let input = document.createElement("input"); + this.linkText.textContent = link ? link.attrs.href : "-empty-"; - // menu item for input for hyperlink url - // TODO: integrate search to allow users to search for a doc to link to - let linkInfo = new MenuItem({ - title: "", - execEvent: "", - class: "button-setting-disabled", - css: "", - render() { - let p = document.createElement("p"); - p.textContent = "Linked to:"; - - input.type = "text"; - input.placeholder = "Enter URL"; - if (targetTitle) input.value = targetTitle; - input.onclick = (e: MouseEvent) => { - input.select(); - input.focus(); - }; - - let div = document.createElement("div"); - div.appendChild(p); - div.appendChild(input); - return div; - }, - enable() { return false; }, - run(p1, p2, p3, event) { - event.stopPropagation(); - } - }); - - // menu item to update/apply the hyperlink to the selected text - let linkApply = new MenuItem({ - title: "", - execEvent: "", - class: "", - css: "", - render() { - let button = document.createElement("button"); - button.className = "link-url-button"; - button.textContent = "Apply hyperlink"; - return button; - }, - enable() { return false; }, - run: (state, dispatch, view, event) => { - event.stopPropagation(); - this.makeLink(input.value, "onRight"); - } - }); - - // menu item to remove the link - // TODO: allow this to be undoable - let self = this; - let deleteLink = new MenuItem({ - title: "Delete link", - execEvent: "", - class: "separated-button", - css: "", - render() { - let button = document.createElement("button"); - button.textContent = "Remove link"; - - let wrapper = document.createElement("div"); - wrapper.appendChild(button); - return wrapper; - }, - enable() { return true; }, - async run() { - self.deleteLink(); - // update link dropdown - let dropdown = await self.createLinkDropdown(); - let newLinkDropdowndom = dropdown.render(self.view).dom; - self._linkDropdownDom && self.tooltip.replaceChild(newLinkDropdowndom, self._linkDropdownDom); - self._linkDropdownDom = newLinkDropdowndom; + this.linkText.onkeydown = (e: KeyboardEvent) => { + if (e.key === "Enter") { + // this.makeLink(this.linkText!.textContent!); + e.stopPropagation(); + e.preventDefault(); } - }); + }; + // this.tooltip.appendChild(this.linkEditor); + } + // updateLinkMenu() { + // if (!this.linkEditor || !this.linkText) { + // this.linkEditor = document.createElement("div"); + // this.linkEditor.className = "ProseMirror-icon menuicon"; + // this.linkDrag = document.createElement("img"); + // this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; + // this.linkDrag.style.width = "13px"; + // this.linkDrag.style.height = "13px"; + // this.linkDrag.title = "Drag to create link"; + // this.linkDrag.id = "link-drag"; + // this.linkDrag.onpointerdown = (e: PointerEvent) => { + // let node = this.view.state.selection.$from.nodeAfter; + // let link = node && node.marks.find(m => m.type.name === "link"); + // if (link) { + // let href: string = link.attrs.href; + // if (href.indexOf(Utils.prepend("/doc/")) === 0) { + // let docid = href.replace(Utils.prepend("/doc/"), ""); + // DocServer.GetRefField(docid).then(action((f: Opt) => { + // if (f instanceof Doc) { + // if (DocumentManager.Instance.getDocumentView(f)) { + // DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false); + // } + // else this.editorProps && this.editorProps.addDocTab(f, undefined, "onRight"); + // } + // })); + // } + // // TODO This should have an else to handle external links + // e.stopPropagation(); + // e.preventDefault(); + // } + // }; + // this.linkEditor.appendChild(this.linkDrag); + // this.tooltip.appendChild(this.linkEditor); + // } - let linkDropdown = new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem; - return linkDropdown; - } + // // let node = this.view.state.selection.$from.nodeAfter; + // // let link = node && node.marks.find(m => m.type.name === "link"); + // // this.linkText.textContent = link ? link.attrs.href : "-empty-"; + + // // this.linkText.onkeydown = (e: KeyboardEvent) => { + // // if (e.key === "Enter") { + // // // this.makeLink(this.linkText!.textContent!); + // // e.stopPropagation(); + // // e.preventDefault(); + // // } + // // }; + // // // this.tooltip.appendChild(this.linkEditor); + // } + // async getTextLinkTargetTitle() { + // let node = this.view.state.selection.$from.nodeAfter; + // let link = node && node.marks.find(m => m.type.name === "link"); + // if (link) { + // let href = link.attrs.href; + // if (href) { + // if (href.indexOf(Utils.prepend("/doc/")) === 0) { + // const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + // if (linkclicked) { + // let linkDoc = await DocServer.GetRefField(linkclicked); + // if (linkDoc instanceof Doc) { + // let anchor1 = await Cast(linkDoc.anchor1, Doc); + // let anchor2 = await Cast(linkDoc.anchor2, Doc); + // let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; + // if (currentDoc && anchor1 && anchor2) { + // if (Doc.AreProtosEqual(currentDoc, anchor1)) return StrCast(anchor2.title); + // if (Doc.AreProtosEqual(currentDoc, anchor2)) return StrCast(anchor2.title); + // } + // } + // } + // } else { + // return href; + // } + // } + // } + // } dragElement(elmnt: HTMLElement) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 29e8b14a8..f92ccf9f5 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -46,17 +46,17 @@ height: 100%; } -.menuicon { - display: inline-block; - border-right: 1px solid rgba(0, 0, 0, 0.2); - color: #888; - line-height: 1; - padding: 0 7px; - margin: 1px; - cursor: pointer; - text-align: center; - min-width: 1.4em; -} +// .menuicon { +// display: inline-block; +// border-right: 1px solid rgba(0, 0, 0, 0.2); +// color: #888; +// line-height: 1; +// padding: 0 7px; +// margin: 1px; +// cursor: pointer; +// text-align: center; +// min-width: 1.4em; +// } .strong, .heading { -- cgit v1.2.3-70-g09d2 From 3a5e787e3a3c42ec3208f9f75bd8bfbac71aeb81 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 10 Oct 2019 11:44:11 -0400 Subject: added dropdowns to link tool and brush tool --- src/client/util/TooltipTextMenu.tsx | 315 +++++++++++++++++++++++++----------- 1 file changed, 221 insertions(+), 94 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index d0bb753ba..ce0237527 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -50,6 +50,7 @@ export class TooltipTextMenu { private linkDrag?: HTMLImageElement; private _linkDropdownDom?: Node; private _brushdom?: Node; + private _brushDropdownDom?: Node; private fontSizeDom?: Node; private fontStyleDom?: Node; @@ -154,10 +155,9 @@ export class TooltipTextMenu { // link menu this.updateLinkMenu(); - // let dropdown = await this.createLinkDropdown(); - // let newLinkDropdowndom = dropdown.render(this.view).dom; - // this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); - // this._linkDropdownDom = newLinkDropdowndom; + let dropdown = await this.createLinkDropdown(); + this._linkDropdownDom = dropdown.render(this.view).dom; + this.tooltip.appendChild(this._linkDropdownDom); // list of font styles this.initFontStyles(); @@ -171,18 +171,18 @@ export class TooltipTextMenu { // init brush tool this._brushdom = this.createBrush().render(this.view).dom; this.tooltip.appendChild(this._brushdom); - // this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom; - // this.tooltip.appendChild(this._brushDropdownDom); + this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom; + this.tooltip.appendChild(this._brushDropdownDom); - // star and pin - this.tooltip.appendChild(this.createLink().render(this.view).dom); + // star + // this.tooltip.appendChild(this.createLink().render(this.view).dom); this.tooltip.appendChild(this.createStar().render(this.view).dom); // this.updateListItemDropdown(":", this.listTypeBtnDom); // - this.updateFromDash(view, undefined, undefined); + await this.updateFromDash(view, undefined, undefined); // TooltipTextMenu.Toolbar = this.wrapper; // dragger @@ -302,11 +302,11 @@ export class TooltipTextMenu { if (!this.linkEditor || !this.linkText) { this.linkEditor = document.createElement("div"); this.linkEditor.className = "ProseMirror-icon menuicon"; - this.linkEditor.style.color = "black"; + // this.linkEditor.style.color = "black"; this.linkText = document.createElement("div"); - this.linkText.style.cssFloat = "left"; - this.linkText.style.marginRight = "5px"; - this.linkText.style.marginLeft = "5px"; + // this.linkText.style.cssFloat = "left"; + // this.linkText.style.marginRight = "5px"; + // this.linkText.style.marginLeft = "5px"; this.linkText.setAttribute("contenteditable", "true"); this.linkText.style.whiteSpace = "nowrap"; this.linkText.style.width = "150px"; @@ -345,9 +345,10 @@ export class TooltipTextMenu { this.linkDrag.style.width = "15px"; this.linkDrag.style.height = "15px"; this.linkDrag.title = "Drag to create link"; - this.linkDrag.style.color = "black"; - this.linkDrag.style.background = "black"; - this.linkDrag.style.cssFloat = "left"; + this.linkDrag.id = "link-drag"; + // this.linkDrag.style.color = "black"; + // this.linkDrag.style.background = "black"; + // this.linkDrag.style.cssFloat = "left"; this.linkDrag.onpointerdown = (e: PointerEvent) => { if (!this.editorProps) return; let dragData = new DragManager.LinkDragData(this.editorProps.Document); @@ -366,7 +367,7 @@ export class TooltipTextMenu { if (proto && docView) { proto.sourceContext = docView.props.ContainingCollectionDoc; } - let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab"); + let text = this.makeLinkToDoc(linkDoc, ctrlKey ? "onRight" : "inTab"); if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) { proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link } @@ -396,81 +397,125 @@ export class TooltipTextMenu { // this.tooltip.appendChild(this.linkEditor); } - // updateLinkMenu() { - // if (!this.linkEditor || !this.linkText) { - // this.linkEditor = document.createElement("div"); - // this.linkEditor.className = "ProseMirror-icon menuicon"; - // this.linkDrag = document.createElement("img"); - // this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; - // this.linkDrag.style.width = "13px"; - // this.linkDrag.style.height = "13px"; - // this.linkDrag.title = "Drag to create link"; - // this.linkDrag.id = "link-drag"; - // this.linkDrag.onpointerdown = (e: PointerEvent) => { - // let node = this.view.state.selection.$from.nodeAfter; - // let link = node && node.marks.find(m => m.type.name === "link"); - // if (link) { - // let href: string = link.attrs.href; - // if (href.indexOf(Utils.prepend("/doc/")) === 0) { - // let docid = href.replace(Utils.prepend("/doc/"), ""); - // DocServer.GetRefField(docid).then(action((f: Opt) => { - // if (f instanceof Doc) { - // if (DocumentManager.Instance.getDocumentView(f)) { - // DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false); - // } - // else this.editorProps && this.editorProps.addDocTab(f, undefined, "onRight"); - // } - // })); - // } - // // TODO This should have an else to handle external links - // e.stopPropagation(); - // e.preventDefault(); - // } - // }; - // this.linkEditor.appendChild(this.linkDrag); - // this.tooltip.appendChild(this.linkEditor); - // } + async getTextLinkTargetTitle() { + let node = this.view.state.selection.$from.nodeAfter; + let link = node && node.marks.find(m => m.type.name === "link"); + // let href = link!.attrs.href; + if (link) { + let href = link.attrs.href; + if (href) { + if (href.indexOf(Utils.prepend("/doc/")) === 0) { + const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + if (linkclicked) { + let linkDoc = await DocServer.GetRefField(linkclicked); + if (linkDoc instanceof Doc) { + let anchor1 = await Cast(linkDoc.anchor1, Doc); + let anchor2 = await Cast(linkDoc.anchor2, Doc); + let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; + if (currentDoc && anchor1 && anchor2) { + if (Doc.AreProtosEqual(currentDoc, anchor1)) { + return StrCast(anchor2.title); + } + if (Doc.AreProtosEqual(currentDoc, anchor2)) { + return StrCast(anchor1.title); + } + } + } + } + } else { + return href; + } + } else { + return link.attrs.title; + } + } + } - // // let node = this.view.state.selection.$from.nodeAfter; - // // let link = node && node.marks.find(m => m.type.name === "link"); - // // this.linkText.textContent = link ? link.attrs.href : "-empty-"; - - // // this.linkText.onkeydown = (e: KeyboardEvent) => { - // // if (e.key === "Enter") { - // // // this.makeLink(this.linkText!.textContent!); - // // e.stopPropagation(); - // // e.preventDefault(); - // // } - // // }; - // // // this.tooltip.appendChild(this.linkEditor); - // } + async createLinkDropdown() { + let targetTitle = await this.getTextLinkTargetTitle(); + let input = document.createElement("input"); + + // menu item for input for hyperlink url + // TODO: integrate search to allow users to search for a doc to link to + let linkInfo = new MenuItem({ + title: "", + execEvent: "", + class: "button-setting-disabled", + css: "", + render() { + let p = document.createElement("p"); + p.textContent = "Linked to:"; + + input.type = "text"; + input.placeholder = "Enter URL"; + console.log(targetTitle); + if (targetTitle) input.value = targetTitle; + input.onclick = (e: MouseEvent) => { + input.select(); + input.focus(); + }; + + let div = document.createElement("div"); + div.appendChild(p); + div.appendChild(input); + return div; + }, + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); + } + }); + + // menu item to update/apply the hyperlink to the selected text + let linkApply = new MenuItem({ + title: "", + execEvent: "", + class: "", + css: "", + render() { + let button = document.createElement("button"); + button.className = "link-url-button"; + button.textContent = "Apply hyperlink"; + return button; + }, + enable() { return false; }, + run: (state, dispatch, view, event) => { + event.stopPropagation(); + this.makeLinkToURL(input.value, "onRight"); + } + }); + + // menu item to remove the link + // TODO: allow this to be undoable + let self = this; + let deleteLink = new MenuItem({ + title: "Delete link", + execEvent: "", + class: "separated-button", + css: "", + render() { + let button = document.createElement("button"); + button.textContent = "Remove link"; + + let wrapper = document.createElement("div"); + wrapper.appendChild(button); + return wrapper; + }, + enable() { return true; }, + async run() { + self.deleteLink(); + // update link dropdown + let dropdown = await self.createLinkDropdown(); + let newLinkDropdowndom = dropdown.render(self.view).dom; + self._linkDropdownDom && self.tooltip.replaceChild(newLinkDropdowndom, self._linkDropdownDom); + self._linkDropdownDom = newLinkDropdowndom; + } + }); - // async getTextLinkTargetTitle() { - // let node = this.view.state.selection.$from.nodeAfter; - // let link = node && node.marks.find(m => m.type.name === "link"); - // if (link) { - // let href = link.attrs.href; - // if (href) { - // if (href.indexOf(Utils.prepend("/doc/")) === 0) { - // const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - // if (linkclicked) { - // let linkDoc = await DocServer.GetRefField(linkclicked); - // if (linkDoc instanceof Doc) { - // let anchor1 = await Cast(linkDoc.anchor1, Doc); - // let anchor2 = await Cast(linkDoc.anchor2, Doc); - // let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; - // if (currentDoc && anchor1 && anchor2) { - // if (Doc.AreProtosEqual(currentDoc, anchor1)) return StrCast(anchor2.title); - // if (Doc.AreProtosEqual(currentDoc, anchor2)) return StrCast(anchor2.title); - // } - // } - // } - // } else { - // return href; - // } - // } - // } - // } + + let linkDropdown = new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem; + return linkDropdown; + } dragElement(elmnt: HTMLElement) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; @@ -532,7 +577,7 @@ export class TooltipTextMenu { // let link = state.schema.mark(state.schema.marks.link, { href: target, location: location }); // } - makeLink = (targetDoc: Doc, location: string): string => { + makeLinkToDoc = (targetDoc: Doc, location: string): string => { let target = Utils.prepend("/doc/" + targetDoc[Id]); let node = this.view.state.selection.$from.nodeAfter; let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location, guid: targetDoc[Id] }); @@ -548,6 +593,15 @@ export class TooltipTextMenu { return ""; } + makeLinkToURL = (target: String, lcoation: string) => { + let node = this.view.state.selection.$from.nodeAfter; + let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location }); + this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link)); + this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link)); + node = this.view.state.selection.$from.nodeAfter; + link = node && node.marks.find(m => m.type.name === "link"); + } + deleteLink = () => { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); @@ -711,7 +765,7 @@ export class TooltipTextMenu { label: "Summarize", icon: icons.join, css: "color:white;", - class: "summarize", + class: "menuicon", execEvent: "", run: (state, dispatch) => { TooltipTextMenu.insertStar(this.view.state, this.view.dispatch); @@ -743,15 +797,21 @@ export class TooltipTextMenu { height: 32, width: 32, path: "M30.828 1.172c-1.562-1.562-4.095-1.562-5.657 0l-5.379 5.379-3.793-3.793-4.243 4.243 3.326 3.326-14.754 14.754c-0.252 0.252-0.358 0.592-0.322 0.921h-0.008v5c0 0.552 0.448 1 1 1h5c0 0 0.083 0 0.125 0 0.288 0 0.576-0.11 0.795-0.329l14.754-14.754 3.326 3.326 4.243-4.243-3.793-3.793 5.379-5.379c1.562-1.562 1.562-4.095 0-5.657zM5.409 30h-3.409v-3.409l14.674-14.674 3.409 3.409-14.674 14.674z" }; + let self = this; return new MenuItem({ title: "Brush tool", label: "Brush tool", icon: icon, css: "color:white;", - class: active ? "brush-active" : "brush", + class: active ? "menuicon-active" : "menuicon", execEvent: "", run: (state, dispatch) => { this.brush_function(state, dispatch); + + // update dropdown with marks + let newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom; + self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom); + self._brushDropdownDom = newBrushDropdowndom; }, active: (state) => { return true; @@ -798,6 +858,67 @@ export class TooltipTextMenu { } + createBrushDropdown(active: boolean = false) { + let label = "Stored marks: "; + if (TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0) { + TooltipTextMenuManager.Instance._brushMarks.forEach((mark: Mark) => { + const markType = mark.type; + label += markType.name; + label += ", "; + }); + label = label.substring(0, label.length - 2); + } else { + label = "No marks are currently stored"; + } + + + let brushInfo = new MenuItem({ + title: "", + label: label, + execEvent: "", + class: "button-setting-disabled", + css: "", + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); + } + }); + + let self = this; + let clearBrush = new MenuItem({ + title: "Clear brush", + execEvent: "", + class: "separated-button", + css: "", + render() { + let button = document.createElement("button"); + button.textContent = "Clear brush"; + + let wrapper = document.createElement("div"); + wrapper.appendChild(button); + return wrapper; + }, + enable() { return true; }, + run() { + TooltipTextMenuManager.Instance._brushIsEmpty = true; + TooltipTextMenuManager.Instance._brushMarks = new Set(); + + // update brush tool + // TODO: this probably isn't very clean + let newBrushdom = self.createBrush().render(self.view).dom; + self._brushdom && self.tooltip.replaceChild(newBrushdom, self._brushdom); + self._brushdom = newBrushdom; + let newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom; + self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom); + self._brushDropdownDom = newBrushDropdowndom; + } + }); + + let hasMarks = TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0; + let brushDom = new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem; + return brushDom; + } + // createCollapse() { // this._collapseBtn = new MenuItem({ // title: "Collapse", @@ -978,7 +1099,7 @@ export class TooltipTextMenu { update(view: EditorView, lastState: EditorState | undefined) { this.updateFromDash(view, lastState, this.editorProps); } //updates the tooltip menu when the selection changes - public updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { + public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { if (!view) { console.log("no editor? why?"); return; @@ -1000,6 +1121,12 @@ export class TooltipTextMenu { } //UPDATE LIST ITEM DROPDOWN + // update link dropdown + let dropdown = await this.createLinkDropdown(); + let newLinkDropdowndom = dropdown.render(this.view).dom; + this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); + this._linkDropdownDom = newLinkDropdowndom; + //UPDATE FONT STYLE DROPDOWN let activeStyles = this.activeMarksOnSelection(this.fontStyles); if (activeStyles !== undefined) { -- cgit v1.2.3-70-g09d2 From 1b0f0e5e434bc23128b50303ffc2b13244ba6325 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 10 Oct 2019 12:01:35 -0400 Subject: started colors on rich text editor --- src/client/util/TooltipTextMenu.tsx | 100 ++++++++++++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 10 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index ce0237527..9dd8e6688 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -21,6 +21,7 @@ import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { updateBullets } from './ProsemirrorExampleTransfer'; import { DocumentDecorations } from '../views/DocumentDecorations'; import { SelectionManager } from './SelectionManager'; +import { PastelSchemaPalette } from '../../new_fields/SchemaHeaderField'; const { toggleMark, setBlockType } = require("prosemirror-commands"); const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); @@ -623,16 +624,6 @@ export class TooltipTextMenu { } - public static insertStar(state: EditorState, dispatch: any) { - if (state.selection.empty) return false; - let mark = state.schema.marks.highlight.create(); - let tr = state.tr; - tr.addMark(state.selection.from, state.selection.to, mark); - let content = tr.selection.content(); - let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); - dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); - return true; - } //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown updateListItemDropdown(label: string, listTypeBtn: any) { @@ -774,6 +765,93 @@ export class TooltipTextMenu { }); } + public static insertStar(state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + let mark = state.schema.marks.highlight.create(); + let tr = state.tr; + tr.addMark(state.selection.from, state.selection.to, mark); + let content = tr.selection.content(); + let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); + dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); + return true; + } + + createColorTool() { + return new MenuItem({ + title: "Summarize", + label: "Summarize", + icon: icons.join, + css: "color:white;", + class: "menuicon", + execEvent: "", + run: (state, dispatch) => { + TooltipTextMenu.insertStar(this.view.state, this.view.dispatch); + } + + }); + } + + insertColor(color: String, state: EditorState, dispatch: any) { + + } + + createColorDropdown() { + // menu item for color picker + let colors = new MenuItem({ + title: "", + execEvent: "", + class: "button-setting-disabled", + css: "", + render() { + let p = document.createElement("p"); + p.textContent = "Change color:"; + + // input.type = "text"; + // input.placeholder = "Enter URL"; + // console.log(targetTitle); + // if (targetTitle) input.value = targetTitle; + // input.onclick = (e: MouseEvent) => { + // input.select(); + // input.focus(); + // }; + + let colorsWrapper = document.createElement("div"); + colorsWrapper.className = "colorPicker-wrapper"; + + let colors = [ + PastelSchemaPalette.get("pink2"), + PastelSchemaPalette.get("purple4"), + PastelSchemaPalette.get("bluegreen1"), + PastelSchemaPalette.get("yellow4"), + PastelSchemaPalette.get("red2"), + PastelSchemaPalette.get("bluegreen7"), + PastelSchemaPalette.get("bluegreen5"), + PastelSchemaPalette.get("orange1"), + "#f1efeb", + "#000" + ]; + + colors.forEach(color => { + let button = document.createElement("button"); + button.className = color === TooltipTextMenuManager.Instance.color ? "colorPicker active" : "colorPicker"; + if (color) button.style.backgroundColor = color; + + }); + + let div = document.createElement("div"); + div.appendChild(p); + return div; + }, + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); + } + }); + + let colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem; + return colorDropdown; + } + deleteLinkItem() { const icon = { height: 16, width: 16, @@ -1307,6 +1385,8 @@ class TooltipTextMenuManager { public _brushMarks: Set | undefined; public _brushIsEmpty: boolean = true; + public color: String = "#000"; + public activeMenu: TooltipTextMenu | undefined; static get Instance() { -- cgit v1.2.3-70-g09d2 From e8095539c208169b1883e8e73686382d8d8c6336 Mon Sep 17 00:00:00 2001 From: Fawn Date: Sat, 12 Oct 2019 17:18:58 -0400 Subject: created color picker for text on rich text menu --- src/client/util/RichTextSchema.tsx | 18 +++++++++ src/client/util/TooltipTextMenu.scss | 21 +++++++++++ src/client/util/TooltipTextMenu.tsx | 73 ++++++++++++++---------------------- 3 files changed, 67 insertions(+), 45 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index a5502577b..e943b71e8 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -308,6 +308,24 @@ export const marks: { [index: string]: MarkSpec } = { } }, + // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text. + color: { + attrs: { + color: { default: "#000" } + }, + inclusive: false, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + return { color: dom.getAttribute("color") }; + } + }], + toDOM(node: any) { + return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', { style: 'color: black' }]; + // ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : + // ["a", { ...node.attrs }, 0]; + } + }, + // :: MarkSpec An emphasis mark. Rendered as an `` element. // Has parse rules that also match `` and `font-style: italic`. em: { diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 11c937471..f94799936 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -336,6 +336,27 @@ } } +.colorPicker-wrapper { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + margin-top: 3px; + margin-left: -3px; + width: calc(100% + 6px); +} + +button.colorPicker { + width: 20px; + height: 20px; + border-radius: 15px !important; + margin: 3px; + border: none !important; + + &.active { + border: 2px solid white !important; + } +} + // @import "../views/globalCssVariables"; // .ProseMirror-textblock-dropdown { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 9dd8e6688..32a910497 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -28,9 +28,11 @@ const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc. export class TooltipTextMenu { + public static Toolbar: HTMLDivElement | undefined; + // editor state private view: EditorView; - private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; + // private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; private fontStyles: MarkType[] = []; private fontSizes: MarkType[] = []; private listTypes: (NodeType | any)[] = []; @@ -39,40 +41,38 @@ export class TooltipTextMenu { private listTypeToIcon: Map = new Map(); private _activeMarks: Mark[] = []; private _marksToDoms: Map = new Map(); - // private _brushMarks?: Set; + private _collapsed: boolean = false; //private link: HTMLAnchorElement; // editor doms public tooltip: HTMLElement = document.createElement("div"); private wrapper: HTMLDivElement = document.createElement("div"); + // editor button doms private linkEditor?: HTMLDivElement; private linkText?: HTMLDivElement; private linkDrag?: HTMLImageElement; private _linkDropdownDom?: Node; private _brushdom?: Node; private _brushDropdownDom?: Node; - private fontSizeDom?: Node; private fontStyleDom?: Node; private listTypeBtnDom?: Node; - private _collapsed: boolean = false; + // private _collapseBtn?: MenuItem; // private _brushIsEmpty: boolean = true; - public static Toolbar: HTMLDivElement | undefined; - constructor(view: EditorView) { this.view = view; - // replace old active menu with this - if (TooltipTextMenuManager.Instance.activeMenu) { - TooltipTextMenuManager.Instance.activeMenu.wrapper.remove(); - } - TooltipTextMenuManager.Instance.activeMenu = this; + // // replace old active menu with this + // if (TooltipTextMenuManager.Instance.activeMenu) { + // TooltipTextMenuManager.Instance.activeMenu.wrapper.remove(); + // } + // TooltipTextMenuManager.Instance.activeMenu = this; // initialize the tooltip this.createTooltip(view); @@ -84,30 +84,10 @@ export class TooltipTextMenu { // positioning? TooltipTextMenu.Toolbar = this.wrapper; - - // // position wrapper - // if (TooltipTextMenuManager.Instance.isPinned) { - // this.wrapper.style.position = "absolute"; - // this.wrapper.style.left = TooltipTextMenuManager.Instance.pinnedX + "px"; - // this.wrapper.style.top = TooltipTextMenuManager.Instance.pinnedY + "px"; - - // // if pinned, append to mainview - // const mainView = document.querySelector("#main-div"); - // mainView && mainView.appendChild(this.wrapper); - // } else { - // this.wrapper.style.position = "absolute"; - // this.wrapper.style.top = (this.wrapper.offsetTop + TooltipTextMenuManager.Instance.unpinnedY) + "px"; - // this.wrapper.style.left = (this.wrapper.offsetLeft + TooltipTextMenuManager.Instance.unpinnedX) + "px"; - - // // add tooltip to outerdiv to circumvent scaling problem - // const outer_div = this.editorProps.outer_div; - // outer_div && outer_div(this.wrapper); - // } - } private async createTooltip(view: EditorView) { - // initialize element + // initialize tooltip dom this.tooltip = document.createElement("div"); this.tooltip.className = "tooltipMenu"; @@ -154,6 +134,10 @@ export class TooltipTextMenu { }); + // color menu + this.tooltip.appendChild(this.createColorTool().render(this.view).dom); + this.tooltip.appendChild(this.createColorDropdown().render(this.view).dom); + // link menu this.updateLinkMenu(); let dropdown = await this.createLinkDropdown(); @@ -785,18 +769,22 @@ export class TooltipTextMenu { class: "menuicon", execEvent: "", run: (state, dispatch) => { - TooltipTextMenu.insertStar(this.view.state, this.view.dispatch); + TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch); } }); } - insertColor(color: String, state: EditorState, dispatch: any) { + public static insertColor(color: String, state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + let colorMark = state.schema.mark(state.schema.marks.color, { color: color }); + dispatch(state.tr.addMark(state.selection.from, state.selection.to, colorMark)); } createColorDropdown() { // menu item for color picker + let self = this; let colors = new MenuItem({ title: "", execEvent: "", @@ -806,15 +794,6 @@ export class TooltipTextMenu { let p = document.createElement("p"); p.textContent = "Change color:"; - // input.type = "text"; - // input.placeholder = "Enter URL"; - // console.log(targetTitle); - // if (targetTitle) input.value = targetTitle; - // input.onclick = (e: MouseEvent) => { - // input.select(); - // input.focus(); - // }; - let colorsWrapper = document.createElement("div"); colorsWrapper.className = "colorPicker-wrapper"; @@ -834,12 +813,16 @@ export class TooltipTextMenu { colors.forEach(color => { let button = document.createElement("button"); button.className = color === TooltipTextMenuManager.Instance.color ? "colorPicker active" : "colorPicker"; - if (color) button.style.backgroundColor = color; - + if (color) { + button.style.backgroundColor = color; + button.onclick = e => TooltipTextMenuManager.Instance.color = color; + } + colorsWrapper.appendChild(button); }); let div = document.createElement("div"); div.appendChild(p); + div.appendChild(colorsWrapper); return div; }, enable() { return false; }, -- cgit v1.2.3-70-g09d2 From 891b9706ddabc0a73ea6b25dc504297d6efb90fe Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 21 Oct 2019 22:03:02 -0400 Subject: big cleanup of layoutStrings, fieldExt, fieldKey, etc --- src/client/apis/youtube/YoutubeBox.tsx | 2 +- src/client/documents/Documents.ts | 55 ++++---- src/client/northstar/dash-nodes/HistogramBox.tsx | 2 +- .../util/Import & Export/DirectoryImportBox.tsx | 2 +- src/client/views/CollectionLinearView.tsx | 4 +- src/client/views/DocComponent.tsx | 45 +++---- src/client/views/DocumentDecorations.tsx | 13 +- src/client/views/MainView.tsx | 5 +- .../views/collections/CollectionSchemaView.tsx | 1 - .../views/collections/CollectionStackingView.tsx | 2 - src/client/views/collections/CollectionSubView.tsx | 9 +- src/client/views/collections/CollectionView.tsx | 18 +-- .../collectionFreeForm/CollectionFreeFormView.tsx | 15 +-- .../collections/collectionFreeForm/MarqueeView.tsx | 24 ++-- src/client/views/linking/LinkFollowBox.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 8 +- src/client/views/nodes/ButtonBox.tsx | 20 +-- .../views/nodes/CollectionFreeFormDocumentView.tsx | 18 +-- src/client/views/nodes/ColorBox.tsx | 10 +- src/client/views/nodes/DocuLinkBox.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 71 ++-------- src/client/views/nodes/FieldView.tsx | 5 +- src/client/views/nodes/FontIconBox.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 27 ++-- src/client/views/nodes/IconBox.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 18 +-- src/client/views/nodes/KeyValueBox.tsx | 5 +- src/client/views/nodes/PDFBox.tsx | 8 +- src/client/views/nodes/PresBox.tsx | 2 +- src/client/views/nodes/QueryBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 40 +++--- src/client/views/nodes/WebBox.tsx | 9 +- src/client/views/pdf/PDFViewer.tsx | 17 ++- .../views/presentationview/PresElementBox.tsx | 148 ++++++++++----------- src/new_fields/Doc.ts | 3 +- src/new_fields/documentSchemas.ts | 51 +++++++ 36 files changed, 319 insertions(+), 348 deletions(-) create mode 100644 src/new_fields/documentSchemas.ts (limited to 'src/client/util') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index d73988bb8..bed812852 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -40,7 +40,7 @@ export class YoutubeBox extends React.Component { @observable curVideoTemplates: VideoTemplate[] = []; - public static LayoutString() { return FieldView.LayoutString(YoutubeBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(YoutubeBox, fieldKey); } /** * When component mounts, last search's results are laoded in based on the back up stored diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 937d3c058..a400e68a3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -84,7 +84,8 @@ export interface DocumentOptions { columnWidth?: number; fontSize?: number; curPage?: number; - currentTimecode?: number; + currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) + displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video) documentText?: string; borderRounding?: string; boxShadow?: string; @@ -119,11 +120,11 @@ export namespace Docs { export namespace Prototypes { - type LayoutSource = { LayoutString: (ext?: string) => string }; + type LayoutSource = { LayoutString: (key: string) => string }; type PrototypeTemplate = { layout: { view: LayoutSource, - ext?: string, // optional extension field for layout source + dataField: string }, options?: Partial }; @@ -133,80 +134,80 @@ export namespace Docs { const TemplateMap: TemplateMap = new Map([ [DocumentType.TEXT, { - layout: { view: FormattedTextBox }, + layout: { view: FormattedTextBox, dataField: data }, options: { height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" } }], [DocumentType.HIST, { - layout: { view: HistogramBox }, + layout: { view: HistogramBox, dataField: data }, options: { height: 300, backgroundColor: "black" } }], [DocumentType.QUERY, { - layout: { view: QueryBox }, + layout: { view: QueryBox, dataField: data }, options: { width: 400 } }], [DocumentType.COLOR, { - layout: { view: ColorBox }, + layout: { view: ColorBox, dataField: data }, options: { nativeWidth: 220, nativeHeight: 300 } }], [DocumentType.IMG, { - layout: { view: ImageBox }, + layout: { view: ImageBox, dataField: data }, options: {} }], [DocumentType.WEB, { - layout: { view: WebBox }, + layout: { view: WebBox, dataField: data }, options: { height: 300 } }], [DocumentType.COL, { - layout: { view: CollectionView }, + layout: { view: CollectionView, dataField: data }, options: { panX: 0, panY: 0, scale: 1, width: 500, height: 500 } }], [DocumentType.KVP, { - layout: { view: KeyValueBox }, + layout: { view: KeyValueBox, dataField: data }, options: { height: 150 } }], [DocumentType.VID, { - layout: { view: VideoBox }, + layout: { view: VideoBox, dataField: data }, options: { currentTimecode: 0 }, }], [DocumentType.AUDIO, { - layout: { view: AudioBox }, + layout: { view: AudioBox, dataField: data }, options: { height: 32 } }], [DocumentType.PDF, { - layout: { view: PDFBox }, + layout: { view: PDFBox, dataField: data }, options: { nativeWidth: 1200, curPage: 1 } }], [DocumentType.ICON, { - layout: { view: IconBox }, + layout: { view: IconBox, dataField: data }, options: { width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) }, }], [DocumentType.IMPORT, { - layout: { view: DirectoryImportBox }, + layout: { view: DirectoryImportBox, dataField: data }, options: { height: 150 } }], [DocumentType.LINKDOC, { data: new List(), - layout: { view: EmptyBox }, + layout: { view: EmptyBox, dataField: data }, }], [DocumentType.YOUTUBE, { - layout: { view: YoutubeBox } + layout: { view: YoutubeBox, dataField: data } }], [DocumentType.BUTTON, { - layout: { view: ButtonBox }, + layout: { view: ButtonBox, dataField: data }, }], [DocumentType.PRES, { - layout: { view: PresBox }, + layout: { view: PresBox, dataField: data }, options: {} }], [DocumentType.FONTICON, { - layout: { view: FontIconBox }, + layout: { view: FontIconBox, dataField: data }, options: { width: 40, height: 40, borderRounding: "100%" }, }], [DocumentType.LINKFOLLOW, { - layout: { view: LinkFollowBox } + layout: { view: LinkFollowBox, dataField: data } }], [DocumentType.PRESELEMENT, { - layout: { view: PresElementBox } + layout: { view: PresElementBox, dataField: data } }], ]); @@ -285,7 +286,7 @@ export namespace Docs { // synthesize the default options, the type and title from computed values and // whatever options pertain to this specific prototype let options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) }; - options.layout = layout.view.LayoutString(); + options.layout = layout.view.LayoutString(layout.dataField); return Doc.assign(new Doc(prototypeId, true), { ...options, baseLayout: options.layout }); } @@ -701,12 +702,12 @@ export namespace DocUtils { linkDocProto.linkDescription = description; linkDocProto.anchor1 = source.doc; - linkDocProto.anchor1Context = source.ctx; - linkDocProto.anchor1Timecode = source.doc.currentTimecode; - linkDocProto.anchor1Groups = new List([]); linkDocProto.anchor2 = target.doc; + linkDocProto.anchor1Context = source.ctx; linkDocProto.anchor2Context = target.ctx; + linkDocProto.anchor1Groups = new List([]); linkDocProto.anchor2Groups = new List([]); + linkDocProto.anchor1Timecode = source.doc.currentTimecode; linkDocProto.anchor2Timecode = target.doc.currentTimecode; linkDocProto.layoutKey1 = DocuLinkBox.LayoutString("anchor1"); linkDocProto.layoutKey2 = DocuLinkBox.LayoutString("anchor2"); diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index b81eafbee..854135648 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -24,7 +24,7 @@ import { Id } from "../../../new_fields/FieldSymbols"; @observer export class HistogramBox extends React.Component { - public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(HistogramBox, fieldStr); } + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(HistogramBox, fieldStr); } private _dropXRef = React.createRef(); private _dropYRef = React.createRef(); private _dropXDisposer?: DragManager.DragDropDisposer; diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index d74b51993..f27d05487 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -50,7 +50,7 @@ export default class DirectoryImportBox extends React.Component @observable private uploading = false; @observable private removeHover = false; - public static LayoutString() { return FieldView.LayoutString(DirectoryImportBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DirectoryImportBox, fieldKey); } constructor(props: FieldViewProps) { super(props); diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 4e03b8c95..1f28ef35d 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -10,7 +10,8 @@ import { Transform } from '../util/Transform'; import "./CollectionLinearView.scss"; import { CollectionViewType } from './collections/CollectionBaseView'; import { CollectionSubView } from './collections/CollectionSubView'; -import { documentSchema, DocumentView } from './nodes/DocumentView'; +import { DocumentView } from './nodes/DocumentView'; +import { documentSchema } from '../../new_fields/documentSchemas'; type LinearDocument = makeInterface<[typeof documentSchema,]>; @@ -75,7 +76,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { (schemaCtor: (doc: Doc) => T) { class Component extends React.Component

{ //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then - @computed - get Document(): T { - return schemaCtor(this.props.Document); - } - @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc!) : Doc.GetProto(this.props.Document); } - @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } + @computed get Document(): T { return schemaCtor(this.props.Document); } + @computed get layoutDoc() { return PositionDocument(Doc.Layout(this.props.Document)); } } return Component; } - -/// DocStaticProps return a base class for React views of document fields that are interactive only when selected (e.g. ColorBox) -interface DocStaticProps { +/// DocStaticProps return a base class for React document views that have data extensions but aren't annotatable (e.g. AudioBox, FormattedTextBox) +interface DocExtendableProps { Document: Doc; DataDoc?: Doc; fieldKey: string; isSelected: () => boolean; renderDepth: number; } -export function DocStaticComponent

(schemaCtor: (doc: Doc) => T) { +export function DocExtendableComponent

(schemaCtor: (doc: Doc) => T) { class Component extends React.Component

{ //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then - @computed - get Document(): T { - return schemaCtor(this.props.Document); - } - @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc!) : Doc.GetProto(this.props.Document); } + @computed get Document(): T { return schemaCtor(this.props.Document); } + @computed get layoutDoc() { return Doc.Layout(this.props.Document); } + @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; } @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } active = () => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected() || this.props.renderDepth === 0);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools } @@ -59,23 +51,20 @@ interface DocAnnotatableProps { isSelected: () => boolean; renderDepth: number; } -export function DocAnnotatableComponent

(schemaCtor: (doc: Doc) => T, fieldExt: string) { +export function DocAnnotatableComponent

(schemaCtor: (doc: Doc) => T) { class Component extends React.Component

{ _isChildActive = false; - _fieldExt = fieldExt; //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then - @computed - get Document(): T { - return schemaCtor(this.props.Document); - } + @computed get Document(): T { return schemaCtor(this.props.Document); } + @computed get layoutDoc() { return Doc.Layout(this.props.Document); } @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; } @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } - @computed get fieldExt() { return this._fieldExt; } + @computed get annotationsKey() { return "annotations"; } @action.bound removeDocument(doc: Doc): boolean { Doc.GetProto(doc).annotationOn = undefined; - let value = this.extensionDoc && Cast(this.extensionDoc[this._fieldExt], listSpec(Doc), []); + let value = this.extensionDoc && Cast(this.extensionDoc[this.annotationsKey], listSpec(Doc), []); let index = value ? Doc.IndexOf(doc, value.map(d => d as Doc), true) : -1; return index !== -1 && value && value.splice(index, 1) ? true : false; } @@ -88,7 +77,7 @@ export function DocAnnotatableComponent

(schema @action.bound addDocument(doc: Doc): boolean { Doc.GetProto(doc).annotationOn = this.props.Document; - return this.extensionDoc && Doc.AddDocToList(this.extensionDoc, this._fieldExt, doc) ? true : false; + return this.extensionDoc && Doc.AddDocToList(this.extensionDoc, this.annotationsKey, doc) ? true : false; } whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 252f90d46..b46caf3ea 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -4,27 +4,26 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCastAsync } from "../../new_fields/Doc"; +import { PositionDocument } from '../../new_fields/documentSchemas'; import { List } from "../../new_fields/List"; import { ObjectField } from '../../new_fields/ObjectField'; -import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; +import { Cast, NumCast, StrCast } from "../../new_fields/Types"; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; -import { emptyFunction, Utils } from "../../Utils"; +import { Utils } from "../../Utils"; import { Docs, DocUtils } from "../documents/Documents"; import { DocumentManager } from "../util/DocumentManager"; import { DragManager } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; +import { TooltipTextMenu } from '../util/TooltipTextMenu'; import { undoBatch, UndoManager } from "../util/UndoManager"; import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; import { CollectionView } from "./collections/CollectionView"; import { DocumentButtonBar } from './DocumentButtonBar'; import './DocumentDecorations.scss'; -import { PositionDocument } from './nodes/CollectionFreeFormDocumentView'; import { DocumentView } from "./nodes/DocumentView"; import { FieldView } from "./nodes/FieldView"; -import { FormattedTextBox } from "./nodes/FormattedTextBox"; import { IconBox } from "./nodes/IconBox"; import React = require("react"); -import { TooltipTextMenu } from '../util/TooltipTextMenu'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -281,7 +280,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd); if (selectedDocs.length > 1) { - this._iconDoc = this._iconDoc ? this._iconDoc : this.createIcon(SelectionManager.SelectedDocuments(), CollectionView.LayoutString()); + this._iconDoc = this._iconDoc ? this._iconDoc : this.createIcon(SelectionManager.SelectedDocuments(), CollectionView.LayoutString("")); this.moveIconDoc(this._iconDoc); } else { this.getIconDoc(selectedDocs[0]).then(icon => icon && this.moveIconDoc(this._iconDoc = icon)); @@ -339,7 +338,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let iconDoc: Doc | undefined = await Cast(doc.minimizedDoc, Doc); if (!iconDoc || !DocumentManager.Instance.getDocumentView(iconDoc)) { - const layout = StrCast(doc.layout, FieldView.LayoutString(DocumentView)); + const layout = StrCast(doc.layout, FieldView.LayoutString(DocumentView, "")); iconDoc = this.createIcon([docView], layout); } return iconDoc; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index d4b92a110..55a61f098 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -270,7 +270,6 @@ export class MainView extends React.Component { doc) { this._heightMap.set(key, sectionHeight); } - get layoutDoc() { return Doc.Layout(this.props.Document); } - get Sections() { if (!this.sectionFilter || this.sectionHeaders instanceof Promise) return new Map(); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 7f16fe9e0..55365de8c 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -41,7 +41,7 @@ export interface SubCollectionViewProps extends CollectionViewProps { ruleProvider: Doc | undefined; children?: never | (() => JSX.Element[]) | React.ReactNode; isAnnotationOverlay?: boolean; - fieldExt: string; + annotationsKey: string; } export function CollectionSubView(schemaCtor: (doc: Doc) => T) { @@ -74,12 +74,15 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { this._childLayoutDisposer && this._childLayoutDisposer(); } + @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc!) : Doc.GetProto(this.props.Document); } + @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } + // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc. // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through // to its children which may be templates. - // The name of the data field comes from fieldExt if it's an extension, or fieldKey otherwise. + // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey' @computed get dataField() { - return this.props.fieldExt ? (this.extensionDoc ? this.extensionDoc[this.props.fieldExt] : undefined) : this.dataDoc[this.props.fieldKey]; + return this.props.annotationsKey ? (this.extensionDoc ? this.extensionDoc[this.props.annotationsKey] : undefined) : this.dataDoc[this.props.fieldKey]; } get childLayoutPairs() { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index f3b0b17f8..f8eb28ade 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -32,7 +32,7 @@ library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faF @observer export class CollectionView extends React.Component { - public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(CollectionView, fieldStr); } + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } private _reactionDisposer: IReactionDisposer | undefined; @observable private _isLightboxOpen = false; @@ -63,18 +63,18 @@ export class CollectionView extends React.Component { private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => { let props = { ...this.props, ...renderProps }; switch (type) { - case CollectionViewType.Schema: return (); + case CollectionViewType.Schema: return (); // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip - case CollectionViewType.Docking: return (); - case CollectionViewType.Tree: return (); - case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (); } - case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (); } - case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (); } - case CollectionViewType.Linear: { return (); } + case CollectionViewType.Docking: return (); + case CollectionViewType.Tree: return (); + case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (); } + case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (); } + case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (); } + case CollectionViewType.Linear: { return (); } case CollectionViewType.Freeform: default: this.props.Document.freeformLayoutEngine = undefined; - return (); + return (); } return (null); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index cc89dc2d4..0419bc3fa 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -25,8 +25,8 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss" import { ContextMenu } from "../../ContextMenu"; import { ContextMenuProps } from "../../ContextMenuItem"; import { InkingCanvas } from "../../InkingCanvas"; -import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView"; -import { documentSchema, DocumentViewProps } from "../../nodes/DocumentView"; +import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; +import { DocumentViewProps } from "../../nodes/DocumentView"; import { FormattedTextBox } from "../../nodes/FormattedTextBox"; import { pageSchema } from "../../nodes/ImageBox"; import { CollectionSubView } from "../CollectionSubView"; @@ -35,6 +35,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); +import { documentSchema, positionSchema } from "../../DocComponent"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -677,13 +678,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y); // if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey. // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document - return ( + return !this.extensionDoc ? (null) :

- + {!this.extensionDoc ? (null) : @@ -694,8 +694,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { {this.overlayViews} -
- ); + ; } } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 3eaad3ad7..e0bf4a2f0 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -19,23 +19,24 @@ import { CollectionViewType } from "../CollectionBaseView"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); +import { SubCollectionViewProps } from "../CollectionSubView"; interface MarqueeViewProps { getContainerTransform: () => Transform; getTransform: () => Transform; - container: CollectionFreeFormView; addDocument: (doc: Doc) => boolean; activeDocuments: () => Doc[]; selectDocuments: (docs: Doc[]) => void; removeDocument: (doc: Doc) => boolean; addLiveTextDocument: (doc: Doc) => void; isSelected: () => boolean; + extensionDoc: Doc; isAnnotationOverlay?: boolean; setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; } @observer -export class MarqueeView extends React.Component +export class MarqueeView extends React.Component { private _mainCont = React.createRef(); @observable _lastX: number = 0; @@ -187,13 +188,13 @@ export class MarqueeView extends React.Component @action onPointerUp = (e: PointerEvent): void => { - if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]); + if (!this.props.active()) this.props.selectDocuments([this.props.Document]); if (this._visible) { let mselect = this.marqueeSelect(); if (!e.shiftKey) { - SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); + SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document); } - this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); + this.props.selectDocuments(mselect.length ? mselect : [this.props.Document]); } this.cleanupInteractions(true); @@ -246,12 +247,11 @@ export class MarqueeView extends React.Component } get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored. - return this.props.container.extensionDoc && Cast(this.props.container.extensionDoc.ink, InkField); + return this.props.extensionDoc && Cast(this.props.extensionDoc.ink, InkField); } set ink(value: InkField | undefined) { - let cprops = this.props.container.props; - this.props.container.extensionDoc && (this.props.container.extensionDoc.ink = value); + this.props.extensionDoc && (this.props.extensionDoc.ink = value); } @undoBatch @@ -290,11 +290,11 @@ export class MarqueeView extends React.Component } let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)", "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",]; - let colorPalette = Cast(this.props.container.props.Document.colorPalette, listSpec("string")); - if (!colorPalette) this.props.container.props.Document.colorPalette = new List(defaultPalette); - let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]); + let colorPalette = Cast(this.props.Document.colorPalette, listSpec("string")); + if (!colorPalette) this.props.Document.colorPalette = new List(defaultPalette); + let palette = Array.from(Cast(this.props.Document.colorPalette, listSpec("string")) as string[]); let usedPaletted = new Map(); - [...this.props.activeDocuments(), this.props.container.props.Document].map(child => { + [...this.props.activeDocuments(), this.props.Document].map(child => { let bg = StrCast(Doc.Layout(child).backgroundColor); if (palette.indexOf(bg) !== -1) { palette.splice(palette.indexOf(bg), 1); diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 32ebe7c61..ef194624a 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -37,7 +37,7 @@ enum FollowOptions { @observer export class LinkFollowBox extends React.Component { - public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkFollowBox, fieldKey); } public static Instance: LinkFollowBox | undefined; @observable static linkDoc: Doc | undefined = undefined; @observable static destinationDoc: Doc | undefined = undefined; diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 3e5deb55b..4c1c3a465 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -4,18 +4,18 @@ import { observer } from "mobx-react"; import "./AudioBox.scss"; import { Cast } from "../../../new_fields/Types"; import { AudioField } from "../../../new_fields/URLField"; -import { DocStaticComponent } from "../DocComponent"; +import { DocExtendableComponent } from "../DocComponent"; import { makeInterface } from "../../../new_fields/Schema"; -import { documentSchema } from "./DocumentView"; +import { documentSchema } from "../../../new_fields/documentSchemas"; type AudioDocument = makeInterface<[typeof documentSchema]>; const AudioDocument = makeInterface(documentSchema); const defaultField: AudioField = new AudioField(new URL("http://techslides.com/demos/samples/sample.mp3")); @observer -export class AudioBox extends DocStaticComponent(AudioDocument) { +export class AudioBox extends DocExtendableComponent(AudioDocument) { - public static LayoutString() { return FieldView.LayoutString(AudioBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); } _ref = React.createRef(); componentDidMount() { diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index b4d33fb0f..1531d825b 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -3,11 +3,11 @@ import { faEdit } from '@fortawesome/free-regular-svg-icons'; import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCastAsync, DocListCast } from '../../../new_fields/Doc'; +import { Doc, DocListCast } from '../../../new_fields/Doc'; import { List } from '../../../new_fields/List'; import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema'; import { ScriptField } from '../../../new_fields/ScriptField'; -import { BoolCast, StrCast, Cast } from '../../../new_fields/Types'; +import { BoolCast, StrCast, Cast, FieldValue } from '../../../new_fields/Types'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { DocComponent } from '../DocComponent'; @@ -15,26 +15,28 @@ import './ButtonBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; import { ContextMenuProps } from '../ContextMenuItem'; import { ContextMenu } from '../ContextMenu'; +import { documentSchema } from '../../../new_fields/documentSchemas'; library.add(faEdit as any); const ButtonSchema = createSchema({ onClick: ScriptField, + buttonParams: listSpec("string"), text: "string" }); -type ButtonDocument = makeInterface<[typeof ButtonSchema]>; -const ButtonDocument = makeInterface(ButtonSchema); +type ButtonDocument = makeInterface<[typeof ButtonSchema, typeof documentSchema]>; +const ButtonDocument = makeInterface(ButtonSchema, documentSchema); @observer export class ButtonBox extends DocComponent(ButtonDocument) { - public static LayoutString() { return FieldView.LayoutString(ButtonBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ButtonBox, fieldKey); } private dropDisposer?: DragManager.DragDropDisposer; @computed get dataDoc() { return this.props.DataDoc && - (BoolCast(this.props.Document.isTemplateField) || BoolCast(this.props.DataDoc.isTemplateField) || + (this.Document.isTemplateField || BoolCast(this.props.DataDoc.isTemplateField) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } @@ -52,7 +54,7 @@ export class ButtonBox extends DocComponent(Butt let funcs: ContextMenuProps[] = []; funcs.push({ description: "Clear Script Params", event: () => { - let params = Cast(this.props.Document.buttonParams, listSpec("string")); + let params = FieldValue(this.Document.buttonParams); params && params.map(p => this.props.Document[p] = undefined); }, icon: "trash" }); @@ -70,13 +72,13 @@ export class ButtonBox extends DocComponent(Butt } // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")") render() { - let params = Cast(this.props.Document.buttonParams, listSpec("string")); + let params = this.Document.buttonParams; let missingParams = params && params.filter(p => this.props.Document[p] === undefined); params && params.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ... return (
-
+
{(this.Document.text || this.Document.title)}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index e3ca02fa4..58cb831f8 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -2,14 +2,15 @@ import { random } from "animejs"; import { computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; -import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { listSpec } from "../../../new_fields/Schema"; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { percent2frac } from "../../../Utils"; import { Transform } from "../../util/Transform"; import { DocComponent } from "../DocComponent"; import "./CollectionFreeFormDocumentView.scss"; -import { documentSchema, DocumentView, DocumentViewProps } from "./DocumentView"; +import { DocumentView, DocumentViewProps } from "./DocumentView"; import React = require("react"); +import { PositionDocument } from "../../../new_fields/documentSchemas"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined; @@ -20,15 +21,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { jitterRotation: number; transition?: string; } -export const positionSchema = createSchema({ - zIndex: "number", - x: "number", - y: "number", - z: "number", -}); - -export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>; -export const PositionDocument = makeInterface(documentSchema, positionSchema); @observer export class CollectionFreeFormDocumentView extends DocComponent(PositionDocument) { @@ -92,8 +84,6 @@ export class CollectionFreeFormDocumentView extends DocComponent this.clusterColor; - get layoutDoc() { return Doc.Layout(this.props.Document); } - @observable _animPos: number[] | undefined = undefined; finalPanelWidth = () => this.dataProvider ? this.dataProvider.width : this.panelWidth(); diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index fdcedb3a5..fda6d64f4 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -4,20 +4,20 @@ import { SketchPicker } from 'react-color'; import { FieldView, FieldViewProps } from './FieldView'; import "./ColorBox.scss"; import { InkingControl } from "../InkingControl"; -import { DocStaticComponent } from "../DocComponent"; -import { documentSchema } from "./DocumentView"; +import { DocExtendableComponent } from "../DocComponent"; import { makeInterface } from "../../../new_fields/Schema"; -import { trace, reaction, observable, action, IReactionDisposer } from "mobx"; +import { reaction, observable, action, IReactionDisposer } from "mobx"; import { SelectionManager } from "../../util/SelectionManager"; import { StrCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; +import { documentSchema } from "../../../new_fields/documentSchemas"; type ColorDocument = makeInterface<[typeof documentSchema]>; const ColorDocument = makeInterface(documentSchema); @observer -export class ColorBox extends DocStaticComponent(ColorDocument) { - public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ColorBox, fieldKey); } +export class ColorBox extends DocExtendableComponent(ColorDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ColorBox, fieldKey); } _selectedDisposer: IReactionDisposer | undefined; _penDisposer: IReactionDisposer | undefined; diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index 22e44948a..7119b0db0 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -7,11 +7,11 @@ import { Utils } from '../../../Utils'; import { DocumentManager } from "../../util/DocumentManager"; import { DragLinksAsDocuments } from "../../util/DragManager"; import { DocComponent } from "../DocComponent"; -import { documentSchema } from "./DocumentView"; import "./DocuLinkBox.scss"; import { FieldView, FieldViewProps } from "./FieldView"; import React = require("react"); import { DocumentType } from "../../documents/DocumentTypes"; +import { documentSchema } from "../../../new_fields/documentSchemas"; type DocLinkSchema = makeInterface<[typeof documentSchema]>; const DocLinkDocument = makeInterface(documentSchema); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9e36f0811..f741dae9a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,29 +1,37 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import * as fa from '@fortawesome/free-solid-svg-icons'; -import { action, computed, runInAction, trace, observable } from "mobx"; +import { action, computed, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as rp from "request-promise"; import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc"; +import { Document } from '../../../new_fields/documentSchemas'; import { Id } from '../../../new_fields/FieldSymbols'; -import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; +import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from '../../../new_fields/ScriptField'; -import { BoolCast, Cast, NumCast, PromiseValue, StrCast, FieldValue } from "../../../new_fields/Types"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { ImageField } from '../../../new_fields/URLField'; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { emptyFunction, returnTrue, Utils, returnTransparent, returnOne } from "../../../Utils"; +import { emptyFunction, returnTransparent, returnTrue, Utils } from "../../../Utils"; +import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from "../../documents/Documents"; +import { DocumentType } from '../../documents/DocumentTypes'; import { ClientUtils } from '../../util/ClientUtils'; import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, dropActionType } from "../../util/DragManager"; import { LinkManager } from '../../util/LinkManager'; +import { Scripting } from '../../util/Scripting'; import { SelectionManager } from "../../util/SelectionManager"; +import SharingManager from '../../util/SharingManager'; import { Transform } from "../../util/Transform"; import { undoBatch, UndoManager } from "../../util/UndoManager"; +import { CollectionViewType } from '../collections/CollectionBaseView'; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; +import { DictationOverlay } from '../DictationOverlay'; import { DocComponent } from "../DocComponent"; import { EditableView } from '../EditableView'; import { OverlayView } from '../OverlayView'; @@ -33,13 +41,6 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); -import { DocumentType } from '../../documents/DocumentTypes'; -import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; -import { ImageField } from '../../../new_fields/URLField'; -import SharingManager from '../../util/SharingManager'; -import { Scripting } from '../../util/Scripting'; -import { DictationOverlay } from '../DictationOverlay'; -import { CollectionViewType } from '../collections/CollectionBaseView'; library.add(fa.faEdit); library.add(fa.faTrash); @@ -67,7 +68,6 @@ library.add(fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCom export interface DocumentViewProps { ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; - fieldKey: string; Document: Doc; DataDoc?: Doc; fitToBox?: boolean; @@ -96,41 +96,6 @@ export interface DocumentViewProps { layoutKey?: string; } -export const documentSchema = createSchema({ - // layout: "string", // this should be a "string" or Doc, but can't do that in schemas, so best to leave it out - title: "string", // document title (can be on either data document or layout) - nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set - nativeHeight: "number", // " - width: "number", // width of document in its container's coordinate system - height: "number", // " - backgroundColor: "string", // background color of document - opacity: "number", // opacity of document - //links: listSpec(Doc), // computed (readonly) list of links associated with this document - dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias" or "copy") - removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped - onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) - onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. - dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. - ignoreAspect: "boolean", // whether aspect ratio should be ignored when laying out or manipulating the document - autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents - isTemplateField: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed - isBackground: "boolean", // whether document is a background element and ignores input events (can only selet with marquee) - type: "string", // enumerated type of document - maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) - lockedPosition: "boolean", // whether the document can be spatially manipulated - inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently - borderRounding: "string", // border radius rounding of document - searchFields: "string", // the search fields to display when this document matches a search in its metadata - heading: "number", // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc) - showCaption: "string", // whether editable caption text is overlayed at the bottom of the document - showTitle: "string", // whether an editable title banner is displayed at tht top of the document - isButton: "boolean", // whether document functions as a button (overiding native interactions of its content) - ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events) -}); - - -type Document = makeInterface<[typeof documentSchema]>; -const Document = makeInterface(documentSchema); @observer export class DocumentView extends DocComponent(Document) { @@ -572,13 +537,6 @@ export class DocumentView extends DocComponent(Docu }); } - - // the document containing the view layout information - will be the Document itself unless the Document has - // a layout field. In that case, all layout information comes from there unless overriden by Document - get layoutDoc(): Document { - return Document(Doc.Layout(this.props.Document)); - } - // does Document set a layout prop setsLayoutProp = (prop: string) => this.props.Document[prop] !== this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)]; // get the a layout prop by first choosing the prop from Document, then falling back to the layout doc otherwise. @@ -599,7 +557,6 @@ export class DocumentView extends DocComponent(Docu return ((Docu } } -Scripting.addGlobal(function toggleDetail(doc: any) { - doc.layoutKey = StrCast(doc.layoutKey, "layout") === "layout" ? "layoutCustom" : "layout"; -}); \ No newline at end of file +Scripting.addGlobal(function toggleDetail(doc: any) { doc.layoutKey = StrCast(doc.layoutKey, "layout") === "layout" ? "layoutCustom" : "layout"; }); \ No newline at end of file diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 7d69b5b51..5108954bb 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -52,9 +52,8 @@ export interface FieldViewProps { @observer export class FieldView extends React.Component { - public static LayoutString(fieldType: { name: string }, fieldStr: string = "data") { - return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"}/>`; - //"" + public static LayoutString(fieldType: { name: string }, fieldStr: string) { + return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"}/>`; //e.g., "" } @computed diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index fd6a475fb..ae9273639 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -17,7 +17,7 @@ type FontIconDocument = makeInterface<[typeof FontIconSchema]>; const FontIconDocument = makeInterface(FontIconSchema); @observer export class FontIconBox extends DocComponent(FontIconDocument) { - public static LayoutString() { return FieldView.LayoutString(FontIconBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); } @observable _foregroundColor = "white"; _ref: React.RefObject = React.createRef(); _backgroundReaction: IReactionDisposer | undefined; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 995fcee17..5c2d39d98 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -33,7 +33,7 @@ import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { DocComponent } from "../DocComponent"; +import { DocExtendableComponent } from "../DocComponent"; import { DocumentButtonBar } from '../DocumentButtonBar'; import { DocumentDecorations } from '../DocumentDecorations'; import { InkingControl } from "../InkingControl"; @@ -44,6 +44,7 @@ import React = require("react"); import { ContextMenuProps } from '../ContextMenuItem'; import { ContextMenu } from '../ContextMenu'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { documentSchema } from '../../../new_fields/documentSchemas'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -62,16 +63,14 @@ const richTextSchema = createSchema({ export const GoogleRef = "googleDocId"; -type RichTextDocument = makeInterface<[typeof richTextSchema]>; -const RichTextDocument = makeInterface(richTextSchema); +type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>; +const RichTextDocument = makeInterface(richTextSchema, documentSchema); type PullHandler = (exportState: Opt, dataDoc: Doc) => void; @observer -export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { - public static LayoutString(fieldStr: string = "data") { - return FieldView.LayoutString(FormattedTextBox, fieldStr); - } +export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); public static Instance: FormattedTextBox; private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined; @@ -135,17 +134,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return true; } - constructor(props: FieldViewProps) { + constructor(props: any) { super(props); FormattedTextBox.Instance = this; } public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } - // the document containing the view layout information - will be the Document itself unless the Document has - // a layout field. In that case, all layout information comes from there unless overriden by Document - @computed get layoutDoc(): Doc { return Doc.Layout(this.props.Document); } - linkOnDeselect: Map = new Map(); doLinkOnDeselect() { @@ -266,8 +261,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe newLayout = Doc.MakeDelegate(draggedDoc); newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`); } - this.props.Document.layoutCustom = newLayout; - this.props.Document.layoutKey = "layoutCustom"; + this.Document.layoutCustom = newLayout; + this.Document.layoutKey = "layoutCustom"; e.stopPropagation(); // embed document when dragging with a userDropAction or an embedDoc flag set } else if (de.data.userDropAction || de.data.embedDoc) { @@ -1015,7 +1010,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let scrollHeight = this._ref.current ? this._ref.current.scrollHeight : 0; if (!this.layoutDoc.isAnimating && this.layoutDoc.autoHeight && scrollHeight !== 0 && getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation - let nh = this.props.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0); + let nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0); let dh = NumCast(this.layoutDoc.height, 0); this.layoutDoc.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); this.dataDoc.nativeHeight = nh ? scrollHeight : undefined; @@ -1052,7 +1047,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onPointerEnter={action(() => this._entered = true)} onPointerLeave={action(() => this._entered = false)} > -
+
{ diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx index 4971f61b7..60f547b1e 100644 --- a/src/client/views/nodes/IconBox.tsx +++ b/src/client/views/nodes/IconBox.tsx @@ -24,7 +24,7 @@ library.add(faFilm, faTag, faTextHeight); @observer export class IconBox extends React.Component { - public static LayoutString() { return FieldView.LayoutString(IconBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(IconBox, fieldKey); } @observable _panelWidth: number = 0; @observable _panelHeight: number = 0; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 4d623a04f..4c2f8e22d 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -20,12 +20,12 @@ import { ContextMenu } from "../../views/ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; import { DocAnnotatableComponent } from '../DocComponent'; import { InkingControl } from '../InkingControl'; -import { documentSchema } from './DocumentView'; import FaceRectangles from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; +import { documentSchema } from '../../../new_fields/documentSchemas'; var requestImageSize = require('../../util/request-image-size'); var path = require('path'); const { Howl } = require('howler'); @@ -38,7 +38,9 @@ library.add(faFileAudio, faAsterisk); export const pageSchema = createSchema({ curPage: "number", fitWidth: "boolean", - rotation: "number" + rotation: "number", + googlePhotosUrl: "string", + googlePhotosTags: "string" }); interface Window { @@ -54,8 +56,8 @@ type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>; const ImageDocument = makeInterface(pageSchema, documentSchema); @observer -export class ImageBox extends DocAnnotatableComponent(ImageDocument, "annotations") { - public static LayoutString(fieldKey: string = "data") { return FieldView.LayoutString(ImageBox, fieldKey); } +export class ImageBox extends DocAnnotatableComponent(ImageDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); } private _imgRef: React.RefObject = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; @observable private _audioState = 0; @@ -251,7 +253,7 @@ export class ImageBox extends DocAnnotatableComponent this.recordAudioAnnotation(); considerGooglePhotosLink = () => { - const remoteUrl = StrCast(this.props.Document.googlePhotosUrl); + const remoteUrl = this.Document.googlePhotosUrl; return !remoteUrl ? (null) : ( { - const tags = StrCast(this.props.Document.googlePhotosTags); + const tags = this.Document.googlePhotosTags; return !tags ? (null) : (); } @@ -287,7 +289,7 @@ export class ImageBox extends DocAnnotatableComponent { + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(KeyValueBox, fieldStr); } + private _mainCont = React.createRef(); private _keyHeader = React.createRef(); - @observable private rows: KeyValuePair[] = []; - public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(KeyValueBox, fieldStr); } + @observable private rows: KeyValuePair[] = []; @observable private _keyInput: string = ""; @observable private _valueInput: string = ""; @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index b1110daf4..396a5356a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -16,18 +16,18 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { DocAnnotatableComponent } from "../DocComponent"; import { PDFViewer } from "../pdf/PDFViewer"; -import { documentSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); +import { documentSchema } from '../../../new_fields/documentSchemas'; type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @observer -export class PDFBox extends DocAnnotatableComponent(PdfDocument, "annotations") { - public static LayoutString(fieldKey: string = "data") { return FieldView.LayoutString(PDFBox, fieldKey); } +export class PDFBox extends DocAnnotatableComponent(PdfDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); } private _keyValue: string = ""; private _valueValue: string = ""; private _scriptValue: string = ""; @@ -232,7 +232,7 @@ export class PDFBox extends DocAnnotatableComponent pinToPres={this.props.pinToPres} addDocument={this.addDocument} ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select} isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged} - fieldKey={this.props.fieldKey} extensionDoc={this.extensionDoc} startupLive={this._initialScale < 2.5 ? true : false} /> + fieldKey={this.props.fieldKey} startupLive={this._initialScale < 2.5 ? true : false} /> {this.settingsPanel()}
); } diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 15fafb022..7ec5d0471 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -31,7 +31,7 @@ library.add(faEdit); @observer export class PresBox extends React.Component { - public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(PresBox, fieldKey); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } _docListChangedReaction: IReactionDisposer | undefined; componentDidMount() { this._docListChangedReaction = reaction(() => { diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx index ced597b59..99b5810fc 100644 --- a/src/client/views/nodes/QueryBox.tsx +++ b/src/client/views/nodes/QueryBox.tsx @@ -18,7 +18,7 @@ library.add(faEdit); @observer export class QueryBox extends React.Component { - public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(QueryBox, fieldKey); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); } _docListChangedReaction: IReactionDisposer | undefined; componentDidMount() { } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 64871ef41..48a699e58 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -1,33 +1,32 @@ import React = require("react"); -import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked, trace } from "mobx"; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { faVideo } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked } from "mobx"; import { observer } from "mobx-react"; import * as rp from 'request-promise'; +import { Doc } from "../../../new_fields/Doc"; import { InkTool } from "../../../new_fields/InkField"; -import { makeInterface, createSchema, listSpec } from "../../../new_fields/Schema"; -import { Cast, FieldValue, NumCast, BoolCast, StrCast } from "../../../new_fields/Types"; +import { createSchema, makeInterface } from "../../../new_fields/Schema"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { Cast, StrCast } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; import { RouteStore } from "../../../server/RouteStore"; -import { Utils, emptyFunction, returnOne } from "../../../Utils"; +import { emptyFunction, returnOne, Utils } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; +import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { DocAnnotatableComponent } from "../DocComponent"; import { DocumentDecorations } from "../DocumentDecorations"; import { InkingControl } from "../InkingControl"; -import { documentSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import "./VideoBox.scss"; -import { library } from "@fortawesome/fontawesome-svg-core"; -import { faVideo } from "@fortawesome/free-solid-svg-icons"; -import { Doc } from "../../../new_fields/Doc"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { positionSchema } from "./CollectionFreeFormDocumentView"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; +import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas"; var path = require('path'); export const timeSchema = createSchema({ - currentTimecode: "number", + currentTimecode: "number", // the current time of a video or other linear, time-based document. Note, should really get set on an extension field, but that's more complicated when it needs to be set since the extension doc needs to be found first }); type VideoDocument = makeInterface<[typeof documentSchema, typeof positionSchema, typeof timeSchema]>; const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema); @@ -35,7 +34,7 @@ const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema); library.add(faVideo); @observer -export class VideoBox extends DocAnnotatableComponent(VideoDocument, "annotations") { +export class VideoBox extends DocAnnotatableComponent(VideoDocument) { static _youtubeIframeCounter: number = 0; private _reactionDisposer?: IReactionDisposer; private _youtubeReactionDisposer?: IReactionDisposer; @@ -49,7 +48,7 @@ export class VideoBox extends DocAnnotatableComponent this._playing ? this.Pause() : this.Play() + onPlayDown = () => this._playing ? this.Pause() : this.Play(); - @action onFullDown = (e: React.PointerEvent) => { this.FullScreen(); e.stopPropagation(); e.preventDefault(); } - @action onSnapshot = (e: React.PointerEvent) => { this.Snapshot(); e.stopPropagation(); e.preventDefault(); } - @action onResetDown = (e: React.PointerEvent) => { this.Pause(); e.stopPropagation(); @@ -309,12 +304,12 @@ export class VideoBox extends DocAnnotatableComponent { this._isResetClick += Math.abs(e.movementX) + Math.abs(e.movementY); this.Seek(Math.max(0, (this.Document.currentTimecode || 0) + Math.sign(e.movementX) * 0.0333)); e.stopImmediatePropagation(); } + @action onResetUp = (e: PointerEvent) => { document.removeEventListener("pointermove", this.onResetMove, true); @@ -322,7 +317,6 @@ export class VideoBox extends DocAnnotatableComponent; const WebDocument = makeInterface(documentSchema); @observer -export class WebBox extends DocAnnotatableComponent(WebDocument, "annotations") { +export class WebBox extends DocAnnotatableComponent(WebDocument) { - public static LayoutString(fieldKey: string = "data") { return FieldView.LayoutString(WebBox, fieldKey); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } @observable private collapsed: boolean = true; @observable private url: string = ""; - get layoutDoc() { return Doc.Layout(this.props.Document); } componentWillMount() { let field = Cast(this.props.Document[this.props.fieldKey], WebField); @@ -199,7 +198,7 @@ export class WebBox extends DocAnnotatableComponent ; - extensionDoc: Doc; PanelWidth: () => number; PanelHeight: () => number; ContentScaling: () => number; @@ -73,7 +72,7 @@ interface IViewerProps { * Handles rendering and virtualization of the pdf */ @observer -export class PDFViewer extends DocAnnotatableComponent(PdfDocument, "annotations") { +export class PDFViewer extends DocAnnotatableComponent(PdfDocument) { static _annotationStyle: any = addStyleSheet(); @observable private _pageSizes: { width: number, height: number }[] = []; @observable private _annotations: Doc[] = []; @@ -109,8 +108,8 @@ export class PDFViewer extends DocAnnotatableComponent this._script.run({ this: anno }, console.log, true).result); + return this.extensionDoc ? DocListCast(this.extensionDoc.annotations).filter( + anno => this._script.run({ this: anno }, console.log, true).result) : []; } @computed get nonDocAnnotations() { @@ -200,7 +199,7 @@ export class PDFViewer extends DocAnnotatableComponent this.props.extensionDoc && DocListCast(this.props.extensionDoc.annotations), + () => this.extensionDoc && DocListCast(this.extensionDoc.annotations), annotations => annotations && annotations.length && this.renderAnnotations(annotations, true), { fireImmediately: true }); @@ -629,10 +628,10 @@ export class PDFViewer extends DocAnnotatableComponent {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) => - )} + )}
(this.Document.scrollHeight || this.Document.nativeHeight || 0)} PanelWidth={() => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document.nativeWidth || 0)} @@ -673,7 +672,7 @@ export class PDFViewer extends DocAnnotatableComponent this._marqueeing; visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96; render() { - return (
{this.pdfViewerDiv} {this.annotationLayer} diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 0824808eb..5f758f496 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -5,18 +5,19 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { documentSchema } from '../../../new_fields/documentSchemas'; import { Id } from "../../../new_fields/FieldSymbols"; -import { BoolCast, NumCast, StrCast } from "../../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, returnOne } from "../../../Utils"; +import { createSchema, makeInterface } from '../../../new_fields/Schema'; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { emptyFunction, returnFalse } from "../../../Utils"; import { DocumentType } from "../../documents/DocumentTypes"; import { Transform } from "../../util/Transform"; import { CollectionViewType } from '../collections/CollectionBaseView'; -import { DocumentView } from "../nodes/DocumentView"; +import { CollectionSchemaPreview } from '../collections/CollectionSchemaView'; +import { DocComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import "./PresElementBox.scss"; import React = require("react"); -import { CollectionSchemaPreview } from '../collections/CollectionSchemaView'; - library.add(faArrowUp); library.add(fileSolid); @@ -24,34 +25,34 @@ library.add(faLocationArrow); library.add(fileRegular as any); library.add(faSearch); library.add(faArrowDown); + +export const presSchema = createSchema({ + presentationTargetDoc: Doc, + presBox: Doc, + presBoxKey: "string", + showButton: "boolean", + navButton: "boolean", + hideTillShownButton: "boolean", + fadeButton: "boolean", + hideAfterButton: "boolean", + groupButton: "boolean", + embedOpen: "boolean" +}); + +type PresDocument = makeInterface<[typeof presSchema, typeof documentSchema]>; +const PresDocument = makeInterface(presSchema, documentSchema); /** * This class models the view a document added to presentation will have in the presentation. * It involves some functionality for its buttons and options. */ @observer -export class PresElementBox extends React.Component { +export class PresElementBox extends DocComponent(PresDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); } - public static LayoutString() { return FieldView.LayoutString(PresElementBox); } - - @computed get myIndex() { return DocListCast(this.presentationDoc[this.presentationFieldKey]).indexOf(this.props.Document); } - @computed get presentationDoc() { return this.props.Document.presBox as Doc; } - @computed get presentationFieldKey() { return StrCast(this.props.Document.presBoxKey); } + @computed get indexInPres() { return DocListCast(this.presentationDoc[this.Document.presBoxKey || ""]).indexOf(this.props.Document); } + @computed get presentationDoc() { return Cast(this.Document.presBox, Doc) as Doc; } + @computed get targetDoc() { return this.Document.presentationTargetDoc as Doc } @computed get currentIndex() { return NumCast(this.presentationDoc.selectedDoc); } - @computed get showButton() { return BoolCast(this.props.Document.showButton); } - @computed get navButton() { return BoolCast(this.props.Document.navButton); } - @computed get hideTillShownButton() { return BoolCast(this.props.Document.hideTillShownButton); } - @computed get fadeButton() { return BoolCast(this.props.Document.fadeButton); } - @computed get hideAfterButton() { return BoolCast(this.props.Document.hideAfterButton); } - @computed get groupButton() { return BoolCast(this.props.Document.groupButton); } - @computed get embedOpen() { return BoolCast(this.props.Document.embedOpen); } - - set embedOpen(value: boolean) { this.props.Document.embedOpen = value; } - set showButton(val: boolean) { this.props.Document.showButton = val; } - set navButton(val: boolean) { this.props.Document.navButton = val; } - set hideTillShownButton(val: boolean) { this.props.Document.hideTillShownButton = val; } - set fadeButton(val: boolean) { this.props.Document.fadeButton = val; } - set hideAfterButton(val: boolean) { this.props.Document.hideAfterButton = val; } - set groupButton(val: boolean) { this.props.Document.groupButton = val; } /** * The function that is called on click to turn Hiding document till press option on/off. @@ -60,14 +61,14 @@ export class PresElementBox extends React.Component { @action onHideDocumentUntilPressClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.hideTillShownButton = !this.hideTillShownButton; - if (!this.hideTillShownButton) { - if (this.myIndex >= this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 1; + this.Document.hideTillShownButton = !this.Document.hideTillShownButton; + if (!this.Document.hideTillShownButton) { + if (this.indexInPres >= this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 1; } } else { - if (this.presentationDoc.presStatus && this.myIndex > this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 0; + if (this.presentationDoc.presStatus && this.indexInPres > this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 0; } } } @@ -80,15 +81,15 @@ export class PresElementBox extends React.Component { @action onHideDocumentAfterPresentedClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.hideAfterButton = !this.hideAfterButton; - if (!this.hideAfterButton) { - if (this.myIndex <= this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 1; + this.Document.hideAfterButton = !this.Document.hideAfterButton; + if (!this.Document.hideAfterButton) { + if (this.indexInPres <= this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 1; } } else { - if (this.fadeButton) this.fadeButton = false; - if (this.presentationDoc.presStatus && this.myIndex < this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 0; + if (this.Document.fadeButton) this.Document.fadeButton = false; + if (this.presentationDoc.presStatus && this.indexInPres < this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 0; } } } @@ -101,15 +102,15 @@ export class PresElementBox extends React.Component { @action onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.fadeButton = !this.fadeButton; - if (!this.fadeButton) { - if (this.myIndex <= this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 1; + this.Document.fadeButton = !this.Document.fadeButton; + if (!this.Document.fadeButton) { + if (this.indexInPres <= this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 1; } } else { - this.hideAfterButton = false; - if (this.presentationDoc.presStatus && (this.myIndex < this.currentIndex)) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 0.5; + this.Document.hideAfterButton = false; + if (this.presentationDoc.presStatus && (this.indexInPres < this.currentIndex) && this.targetDoc) { + this.targetDoc.opacity = 0.5; } } } @@ -120,10 +121,10 @@ export class PresElementBox extends React.Component { @action onNavigateDocumentClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.navButton = !this.navButton; - if (this.navButton) { - this.showButton = false; - if (this.currentIndex === this.myIndex) { + this.Document.navButton = !this.Document.navButton; + if (this.Document.navButton) { + this.Document.showButton = false; + if (this.currentIndex === this.indexInPres) { this.props.focus(this.props.Document); } } @@ -136,12 +137,12 @@ export class PresElementBox extends React.Component { onZoomDocumentClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.showButton = !this.showButton; - if (!this.showButton) { + this.Document.showButton = !this.Document.showButton; + if (!this.Document.showButton) { this.props.Document.viewScale = 1; } else { - this.navButton = false; - if (this.currentIndex === this.myIndex) { + this.Document.navButton = false; + if (this.currentIndex === this.indexInPres) { this.props.focus(this.props.Document); } } @@ -151,13 +152,12 @@ export class PresElementBox extends React.Component { */ ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord]; - get layoutDoc() { return Doc.Layout(this.props.Document); } /** * The function that is responsible for rendering the a preview or not for this * presentation element. */ renderEmbeddedInline = () => { - if (!this.embedOpen || !(this.props.Document.presentationTargetDoc instanceof Doc)) { + if (!this.Document.embedOpen || !this.targetDoc) { return (null); } @@ -170,8 +170,9 @@ export class PresElementBox extends React.Component { width: propDocWidth === 0 ? "auto" : propDocWidth * scale(), }}> { } render() { - let p = this.props; - let treecontainer = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.viewType === CollectionViewType.Tree; - let className = "presElementBox-item" + (this.currentIndex === this.myIndex ? " presElementBox-selected" : ""); + let className = "presElementBox-item" + (this.currentIndex === this.indexInPres ? " presElementBox-selected" : ""); let pbi = "presElementBox-interaction"; return ( -
{ p.focus(p.Document); e.stopPropagation(); }}> +
{ this.props.focus(this.props.Document); e.stopPropagation(); }}> {treecontainer ? (null) : <> - {`${this.myIndex + 1}. ${p.Document.title}`} + {`${this.indexInPres + 1}. ${this.Document.title}`} - +
- - } - - - - - - - + } + + + + + + +
{this.renderEmbeddedInline()} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index c6f654c33..08cb66d5f 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -645,7 +645,8 @@ export namespace Doc { @observable BrushedDoc: ObservableMap = new ObservableMap(); } - // returns the active layout document for 'doc'. + // the document containing the view layout information - will be the Document itself unless the Document has + // a layout field. In that case, all layout information comes from there unless overriden by Document export function Layout(doc: Doc) { return Doc.LayoutField(doc) instanceof Doc ? doc[StrCast(doc.layoutKey, "layout")] as Doc : doc; } export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; } const manager = new DocData(); diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts new file mode 100644 index 000000000..8c3b62067 --- /dev/null +++ b/src/new_fields/documentSchemas.ts @@ -0,0 +1,51 @@ +import { makeInterface, createSchema, listSpec } from "./Schema"; +import { ScriptField } from "./ScriptField"; +import { Doc } from "./Doc"; + +export const documentSchema = createSchema({ + // layout: "string", // this should be a "string" or Doc, but can't do that in schemas, so best to leave it out + layoutKey: "string", // holds the field key for the field that actually holds the current lyoat + layoutCustom: Doc, // used to hold a custom layout (there's nothing special about this field .. any field could hold a custom layout that can be selected by setting 'layoutKey') + title: "string", // document title (can be on either data document or layout) + nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set + nativeHeight: "number", // " + width: "number", // width of document in its container's coordinate system + height: "number", // " + color: "string", // foreground color of document + backgroundColor: "string", // background color of document + opacity: "number", // opacity of document + //links: listSpec(Doc), // computed (readonly) list of links associated with this document + dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias" or "copy") + removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped + onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) + onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. + dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. + ignoreAspect: "boolean", // whether aspect ratio should be ignored when laying out or manipulating the document + autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents + isTemplateField: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed + isBackground: "boolean", // whether document is a background element and ignores input events (can only selet with marquee) + type: "string", // enumerated type of document + maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) + lockedPosition: "boolean", // whether the document can be spatially manipulated + inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently + borderRounding: "string", // border radius rounding of document + searchFields: "string", // the search fields to display when this document matches a search in its metadata + heading: "number", // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc) + showCaption: "string", // whether editable caption text is overlayed at the bottom of the document + showTitle: "string", // whether an editable title banner is displayed at tht top of the document + isButton: "boolean", // whether document functions as a button (overiding native interactions of its content) + ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events) +}); + +export const positionSchema = createSchema({ + zIndex: "number", + x: "number", + y: "number", + z: "number", +}); + +export type Document = makeInterface<[typeof documentSchema]>; +export const Document = makeInterface(documentSchema); + +export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>; +export const PositionDocument = makeInterface(documentSchema, positionSchema); -- cgit v1.2.3-70-g09d2 From 3b16a81e21942684d63f175b60629f85070715e1 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 21 Oct 2019 23:55:19 -0400 Subject: got rid of CollectionBaseView --- src/client/documents/Documents.ts | 2 +- src/client/util/DictationManager.ts | 2 +- src/client/util/DropConverter.ts | 2 +- src/client/views/CollectionLinearView.tsx | 2 +- src/client/views/MainView.tsx | 4 +- .../views/collections/CollectionBaseView.scss | 26 --- .../views/collections/CollectionBaseView.tsx | 180 -------------------- .../views/collections/CollectionTreeView.tsx | 2 +- src/client/views/collections/CollectionView.scss | 26 +++ src/client/views/collections/CollectionView.tsx | 183 ++++++++++++++++----- .../views/collections/CollectionViewChromes.tsx | 2 +- .../views/collections/ParentDocumentSelector.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/linking/LinkFollowBox.tsx | 2 +- src/client/views/nodes/ButtonBox.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/PresBox.tsx | 2 +- .../views/presentationview/PresElementBox.tsx | 4 +- src/client/views/search/SearchItem.tsx | 2 +- 20 files changed, 186 insertions(+), 266 deletions(-) delete mode 100644 src/client/views/collections/CollectionBaseView.scss delete mode 100644 src/client/views/collections/CollectionBaseView.tsx create mode 100644 src/client/views/collections/CollectionView.scss (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a400e68a3..b1406d5e1 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -2,7 +2,7 @@ import { HistogramField } from "../northstar/dash-fields/HistogramField"; import { HistogramBox } from "../northstar/dash-nodes/HistogramBox"; import { HistogramOperation } from "../northstar/operations/HistogramOperation"; import { CollectionView } from "../views/collections/CollectionView"; -import { CollectionViewType } from "../views/collections/CollectionBaseView"; +import { CollectionViewType } from "../views/collections/CollectionView"; import { AudioBox } from "../views/nodes/AudioBox"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { ImageBox } from "../views/nodes/ImageBox"; diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index ae991635f..6bbd3d0ed 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -6,7 +6,7 @@ import { DocumentType } from "../documents/DocumentTypes"; import { Doc, Opt } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { Docs } from "../documents/Documents"; -import { CollectionViewType } from "../views/collections/CollectionBaseView"; +import { CollectionViewType } from "../views/collections/CollectionView"; import { Cast, CastCtor } from "../../new_fields/Types"; import { listSpec } from "../../new_fields/Schema"; import { AudioField, ImageField } from "../../new_fields/URLField"; diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index eea3da1bc..6b53333d7 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -1,5 +1,5 @@ import { DragManager } from "./DragManager"; -import { CollectionViewType } from "../views/collections/CollectionBaseView"; +import { CollectionViewType } from "../views/collections/CollectionView"; import { Doc, DocListCast } from "../../new_fields/Doc"; import { DocumentType } from "../documents/DocumentTypes"; import { ObjectField } from "../../new_fields/ObjectField"; diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 1f28ef35d..31a518a6c 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -8,7 +8,7 @@ import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from ' import { DragManager } from '../util/DragManager'; import { Transform } from '../util/Transform'; import "./CollectionLinearView.scss"; -import { CollectionViewType } from './collections/CollectionBaseView'; +import { CollectionViewType } from './collections/CollectionView'; import { CollectionSubView } from './collections/CollectionSubView'; import { DocumentView } from './nodes/DocumentView'; import { documentSchema } from '../../new_fields/documentSchemas'; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 55a61f098..26e70c5c7 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -24,7 +24,7 @@ import { HistoryUtil } from '../util/History'; import SharingManager from '../util/SharingManager'; import { Transform } from '../util/Transform'; import { CollectionLinearView } from './CollectionLinearView'; -import { CollectionBaseView, CollectionViewType } from './collections/CollectionBaseView'; +import { CollectionViewType, CollectionView } from './collections/CollectionView'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { ContextMenu } from './ContextMenu'; import { DictationOverlay } from './DictationOverlay'; @@ -228,7 +228,7 @@ export class MainView extends React.Component { if (!state.nro) { DocServer.Control.makeReadOnly(); } - CollectionBaseView.SetSafeMode(true); + CollectionView.SetSafeMode(true); } else if (state.nro || state.nro === null || state.readonly === false) { } else if (doc.readOnly) { DocServer.Control.makeReadOnly(); diff --git a/src/client/views/collections/CollectionBaseView.scss b/src/client/views/collections/CollectionBaseView.scss deleted file mode 100644 index aff965469..000000000 --- a/src/client/views/collections/CollectionBaseView.scss +++ /dev/null @@ -1,26 +0,0 @@ -@import "../globalCssVariables"; - -#collectionBaseView { - border-width: 0; - border-color: $light-color-secondary; - border-style: solid; - border-radius: 0 0 $border-radius $border-radius; - box-sizing: border-box; - border-radius: inherit; - width: 100%; - height: 100%; - overflow: auto; -} - -#google-tags { - transition: all 0.5s ease 0s; - width: 30px; - height: 30px; - position: absolute; - bottom: 15px; - left: 15px; - border: 2px solid black; - border-radius: 50%; - padding: 3px; - background: white; -} \ No newline at end of file diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx deleted file mode 100644 index 9f3d33e6f..000000000 --- a/src/client/views/collections/CollectionBaseView.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { action, computed, observable } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { DateField } from '../../../new_fields/DateField'; -import { Doc, DocListCast } from '../../../new_fields/Doc'; -import { listSpec } from '../../../new_fields/Schema'; -import { BoolCast, Cast, FieldValue, PromiseValue, StrCast } from '../../../new_fields/Types'; -import { ImageField } from '../../../new_fields/URLField'; -import { DocumentManager } from '../../util/DocumentManager'; -import { SelectionManager } from '../../util/SelectionManager'; -import { ContextMenu } from '../ContextMenu'; -import { FieldViewProps } from '../nodes/FieldView'; -import './CollectionBaseView.scss'; - -export enum CollectionViewType { - Invalid, - Freeform, - Schema, - Docking, - Tree, - Stacking, - Masonry, - Pivot, - Linear, -} - -export namespace CollectionViewType { - - const stringMapping = new Map([ - ["invalid", CollectionViewType.Invalid], - ["freeform", CollectionViewType.Freeform], - ["schema", CollectionViewType.Schema], - ["docking", CollectionViewType.Docking], - ["tree", CollectionViewType.Tree], - ["stacking", CollectionViewType.Stacking], - ["masonry", CollectionViewType.Masonry], - ["pivot", CollectionViewType.Pivot], - ["linear", CollectionViewType.Linear] - ]); - - export const valueOf = (value: string) => { - return stringMapping.get(value.toLowerCase()); - }; - -} - -export interface CollectionRenderProps { - addDocument: (document: Doc) => boolean; - removeDocument: (document: Doc) => boolean; - moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; - active: () => boolean; - whenActiveChanged: (isActive: boolean) => void; -} - -export interface CollectionViewProps extends FieldViewProps { - onContextMenu?: (e: React.MouseEvent) => void; - children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | JSX.Element[] | null | (JSX.Element | null)[]; - className?: string; - contentRef?: React.Ref; -} - -@observer -export class CollectionBaseView extends React.Component { - @observable private static _safeMode = false; - static InSafeMode() { return this._safeMode; } - static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } - get collectionViewType(): CollectionViewType | undefined { - let Document = this.props.Document; - let viewField = Cast(Document.viewType, "number"); - if (CollectionBaseView._safeMode) { - if (viewField === CollectionViewType.Freeform) { - return CollectionViewType.Tree; - } - if (viewField === CollectionViewType.Invalid) { - return CollectionViewType.Freeform; - } - } - if (viewField !== undefined) { - return viewField; - } else { - return CollectionViewType.Invalid; - } - } - - active = (): boolean => { - var isSelected = this.props.isSelected(); - return isSelected || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0; - } - - //TODO should this be observable? - private _isChildActive = false; - whenActiveChanged = (isActive: boolean) => { - this._isChildActive = isActive; - this.props.whenActiveChanged(isActive); - } - - - @action.bound - addDocument(doc: Doc): boolean { - let targetDataDoc = Doc.GetProto(this.props.Document); - let targetField = this.props.fieldKey; - Doc.AddDocToList(targetDataDoc, targetField, doc); - let extension = Doc.fieldExtensionDoc(targetDataDoc, targetField); // set metadata about the field being rendered (ie, the set of documents) on an extension field for that field - extension && (extension.lastModified = new DateField(new Date(Date.now()))); - Doc.GetProto(doc).lastOpened = new DateField; - return true; - } - - @action.bound - removeDocument(doc: Doc): boolean { - let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView); - docView && SelectionManager.DeselectDoc(docView); - //TODO This won't create the field if it doesn't already exist - let targetDataDoc = this.props.Document; - let targetField = this.props.fieldKey; - let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); - let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); - index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); - - if (index !== -1) { - value.splice(index, 1); - - // SelectionManager.DeselectAll() - ContextMenu.Instance.clearItems(); - return true; - } - return false; - } - - // this is called with the document that was dragged and the collection to move it into. - // if the target collection is the same as this collection, then the move will be allowed. - // otherwise, the document being moved must be able to be removed from its container before - // moving it into the target. - @action.bound - moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { - if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { - return true; - } - return this.removeDocument(doc) ? addDocument(doc) : false; - } - - showIsTagged = () => { - const children = DocListCast(this.props.Document.data); - const imageProtos = children.filter(doc => Cast(doc.data, ImageField)).map(Doc.GetProto); - const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags); - if (allTagged) { - return ( - - ); - } - return (null); - } - - render() { - const props: CollectionRenderProps = { - addDocument: this.addDocument, - removeDocument: this.removeDocument, - moveDocument: this.moveDocument, - active: this.active, - whenActiveChanged: this.whenActiveChanged, - }; - const viewtype = this.collectionViewType; - return ( -
- {this.showIsTagged()} - {viewtype !== undefined ? this.props.children(viewtype, props) : (null)} -
- ); - } - -} diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 2cad41acb..47c355fc8 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -23,7 +23,7 @@ import { EditableView } from "../EditableView"; import { MainView } from '../MainView'; import { KeyValueBox } from '../nodes/KeyValueBox'; import { Templates } from '../Templates'; -import { CollectionViewType } from './CollectionBaseView'; +import { CollectionViewType } from './CollectionView'; import { CollectionSchemaPreview } from './CollectionSchemaView'; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss new file mode 100644 index 000000000..e4187e4d6 --- /dev/null +++ b/src/client/views/collections/CollectionView.scss @@ -0,0 +1,26 @@ +@import "../globalCssVariables"; + +.collectionView { + border-width: 0; + border-color: $light-color-secondary; + border-style: solid; + border-radius: 0 0 $border-radius $border-radius; + box-sizing: border-box; + border-radius: inherit; + width: 100%; + height: 100%; + overflow: auto; +} + +#google-tags { + transition: all 0.5s ease 0s; + width: 30px; + height: 30px; + position: absolute; + bottom: 15px; + left: 15px; + border: 2px solid black; + border-radius: 50%; + padding: 3px; + background: white; +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index f8eb28ade..8d5694bf0 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -5,12 +5,9 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from 'mo import { observer } from "mobx-react"; import * as React from 'react'; import { Id } from '../../../new_fields/FieldSymbols'; -import { StrCast, Cast } from '../../../new_fields/Types'; +import { StrCast, BoolCast, Cast } from '../../../new_fields/Types'; import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; import { ContextMenu } from "../ContextMenu"; -import { ContextMenuProps } from '../ContextMenuItem'; -import { FieldView, FieldViewProps } from '../nodes/FieldView'; -import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from './CollectionBaseView'; import { CollectionDockingView } from "./CollectionDockingView"; import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; @@ -26,18 +23,75 @@ import { DocListCast } from '../../../new_fields/Doc'; import Lightbox from 'react-image-lightbox-with-rotate'; import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app export const COLLECTION_BORDER_WIDTH = 2; - +import { DateField } from '../../../new_fields/DateField'; +import { Doc, } from '../../../new_fields/Doc'; +import { listSpec } from '../../../new_fields/Schema'; +import { DocumentManager } from '../../util/DocumentManager'; +import { SelectionManager } from '../../util/SelectionManager'; +import './CollectionView.scss'; +import { FieldViewProps, FieldView } from '../nodes/FieldView'; library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy); +export enum CollectionViewType { + Invalid, + Freeform, + Schema, + Docking, + Tree, + Stacking, + Masonry, + Pivot, + Linear, +} + +export namespace CollectionViewType { + const stringMapping = new Map([ + ["invalid", CollectionViewType.Invalid], + ["freeform", CollectionViewType.Freeform], + ["schema", CollectionViewType.Schema], + ["docking", CollectionViewType.Docking], + ["tree", CollectionViewType.Tree], + ["stacking", CollectionViewType.Stacking], + ["masonry", CollectionViewType.Masonry], + ["pivot", CollectionViewType.Pivot], + ["linear", CollectionViewType.Linear] + ]); + + export const valueOf = (value: string) => stringMapping.get(value.toLowerCase()); +} + +export interface CollectionRenderProps { + addDocument: (document: Doc) => boolean; + removeDocument: (document: Doc) => boolean; + moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; + active: () => boolean; + whenActiveChanged: (isActive: boolean) => void; +} + @observer export class CollectionView extends React.Component { - public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } private _reactionDisposer: IReactionDisposer | undefined; + private _isChildActive = false; //TODO should this be observable? @observable private _isLightboxOpen = false; @observable private _curLightboxImg = 0; @observable private _collapsed = true; + @observable private static _safeMode = false; + public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } + + get collectionViewType(): CollectionViewType | undefined { + let viewField = Cast(this.props.Document.viewType, "number"); + if (CollectionView._safeMode) { + if (viewField === CollectionViewType.Freeform) { + return CollectionViewType.Tree; + } + if (viewField === CollectionViewType.Invalid) { + return CollectionViewType.Freeform; + } + } + return viewField === undefined ? CollectionViewType.Invalid : viewField; + } componentDidMount = () => { this._reactionDisposer = reaction(() => StrCast(this.props.Document.chromeStatus), @@ -51,32 +105,73 @@ export class CollectionView extends React.Component { }); } - componentWillUnmount = () => { - this._reactionDisposer && this._reactionDisposer(); + componentWillUnmount = () => this._reactionDisposer && this._reactionDisposer(); + + // bcz: Argh? What's the height of the collection chromes?? + chromeHeight = () => (this.props.ChromeHeight ? this.props.ChromeHeight() : 0) + (this.props.Document.chromeStatus === "enabled" ? -60 : 0); + + active = () => this.props.isSelected() || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0; + + whenActiveChanged = (isActive: boolean) => { this.props.whenActiveChanged(this._isChildActive = isActive); }; + + @action.bound + addDocument(doc: Doc): boolean { + let targetDataDoc = Doc.GetProto(this.props.Document); + Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc); + let extension = Doc.fieldExtensionDoc(targetDataDoc, this.props.fieldKey); // set metadata about the field being rendered (ie, the set of documents) on an extension field for that field + extension && (extension.lastModified = new DateField(new Date(Date.now()))); + Doc.GetProto(doc).lastOpened = new DateField; + return true; + } + + @action.bound + removeDocument(doc: Doc): boolean { + let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView); + docView && SelectionManager.DeselectDoc(docView); + let value = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []); + let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); + index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); + + ContextMenu.Instance.clearItems(); + if (index !== -1) { + value.splice(index, 1); + return true; + } + return false; + } + + // this is called with the document that was dragged and the collection to move it into. + // if the target collection is the same as this collection, then the move will be allowed. + // otherwise, the document being moved must be able to be removed from its container before + // moving it into the target. + @action.bound + moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { + if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { + return true; + } + return this.removeDocument(doc) ? addDocument(doc) : false; } - // bcz: Argh? What's the height of the collection chomes?? - chromeHeight = () => { - return (this.props.ChromeHeight ? this.props.ChromeHeight() : 0) + (this.props.Document.chromeStatus === "enabled" ? -60 : 0); + showIsTagged = () => { + const children = DocListCast(this.props.Document[this.props.fieldKey]); + const imageProtos = children.filter(doc => Cast(doc.data, ImageField)).map(Doc.GetProto); + const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags); + return !allTagged ? (null) : ; } private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => { - let props = { ...this.props, ...renderProps }; + let props = { ...this.props, ...renderProps, chromeCollapsed: this._collapsed, ChromeHeight: this.chromeHeight, CollectionView: this, annotationsKey: "" }; switch (type) { - case CollectionViewType.Schema: return (); - // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip - case CollectionViewType.Docking: return (); - case CollectionViewType.Tree: return (); - case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (); } - case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (); } - case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (); } - case CollectionViewType.Linear: { return (); } + case CollectionViewType.Schema: return (); + case CollectionViewType.Docking: return (); + case CollectionViewType.Tree: return (); + case CollectionViewType.Linear: { return (); } + case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (); } + case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (); } + case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (); } case CollectionViewType.Freeform: - default: - this.props.Document.freeformLayoutEngine = undefined; - return (); + default: { this.props.Document.freeformLayoutEngine = undefined; return (); } } - return (null); } @action @@ -87,22 +182,18 @@ export class CollectionView extends React.Component { private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => { // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip - if (this.props.Document.chromeStatus === "disabled" || type === CollectionViewType.Docking) { - return [(null), this.SubViewHelper(type, renderProps)]; - } - return [ - , - this.SubViewHelper(type, renderProps) - ]; + let chrome = this.props.Document.chromeStatus === "disabled" || type === CollectionViewType.Docking ? (null) : + ; + return [chrome, this.SubViewHelper(type, renderProps)]; } onContextMenu = (e: React.MouseEvent): void => { if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 let existingVm = ContextMenu.Instance.findByDescription("View Modes..."); - let subItems: ContextMenuProps[] = existingVm && "subitems" in existingVm ? existingVm.subitems : []; + let subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : []; subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; }, icon: "signature" }); - if (CollectionBaseView.InSafeMode()) { + if (CollectionView._safeMode) { ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" }); } subItems.push({ description: "Schema", event: () => this.props.Document.viewType = CollectionViewType.Schema, icon: "th-list" }); @@ -126,7 +217,7 @@ export class CollectionView extends React.Component { !existingVm && ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" }); let existing = ContextMenu.Instance.findByDescription("Layout..."); - let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; + let layoutItems = existing && "subitems" in existing ? existing.subitems : []; layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }); !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" }); ContextMenu.Instance.addItem({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) }); @@ -142,15 +233,23 @@ export class CollectionView extends React.Component { onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)} onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />); } - render() { - return (<> - - {this.SubView} - - + const props: CollectionRenderProps = { + addDocument: this.addDocument, + removeDocument: this.removeDocument, + moveDocument: this.moveDocument, + active: this.active, + whenActiveChanged: this.whenActiveChanged, + }; + return (
+ {this.showIsTagged()} + {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)} {this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => d.type === DocumentType.IMG).map(d => Cast(d.data, ImageField) ? Cast(d.data, ImageField)!.url.href : ""))} - - ); +
); } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index dd5e630e4..cfc6c2a3f 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -14,7 +14,7 @@ import { undoBatch } from "../../util/UndoManager"; import { EditableView } from "../EditableView"; import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss"; import { DocLike } from "../MetadataEntryMenu"; -import { CollectionViewType } from "./CollectionBaseView"; +import { CollectionViewType } from "./CollectionView"; import { CollectionView } from "./CollectionView"; import "./CollectionViewChromes.scss"; import * as Autosuggest from 'react-autosuggest'; diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index 7f2913214..8b6fa330c 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -7,7 +7,7 @@ import { Id } from "../../../new_fields/FieldSymbols"; import { SearchUtil } from "../../util/SearchUtil"; import { CollectionDockingView } from "./CollectionDockingView"; import { NumCast } from "../../../new_fields/Types"; -import { CollectionViewType } from "./CollectionBaseView"; +import { CollectionViewType } from "./CollectionView"; import { DocumentButtonBar } from "../DocumentButtonBar"; import { DocumentManager } from "../../util/DocumentManager"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0419bc3fa..d7350fe1a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -35,7 +35,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import { documentSchema, positionSchema } from "../../DocComponent"; +import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index e0bf4a2f0..4f86eeb2b 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -15,7 +15,7 @@ import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; import { InkingCanvas } from "../../InkingCanvas"; import { PreviewCursor } from "../../PreviewCursor"; -import { CollectionViewType } from "../CollectionBaseView"; +import { CollectionViewType } from "../CollectionView"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index ef194624a..efe2c7f2a 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -5,7 +5,7 @@ import { FieldViewProps, FieldView } from "../nodes/FieldView"; import { Doc, DocListCastAsync, Opt } from "../../../new_fields/Doc"; import { undoBatch } from "../../util/UndoManager"; import { NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types"; -import { CollectionViewType } from "../collections/CollectionBaseView"; +import { CollectionViewType } from "../collections/CollectionView"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { SelectionManager } from "../../util/SelectionManager"; import { DocumentManager } from "../../util/DocumentManager"; diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index 1531d825b..beb2b30fd 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -66,7 +66,8 @@ export class ButtonBox extends DocComponent(Butt @action drop = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData && e.target) { - this.props.Document[(e.target as any).textContent] = new List(de.data.droppedDocuments); + this.props.Document[(e.target as any).textContent] = new List(de.data.droppedDocuments.map((d, i) => + d.onDragStart ? de.data.draggedDocuments[i] : d)); e.stopPropagation(); } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f741dae9a..05080b824 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -26,7 +26,7 @@ import { SelectionManager } from "../../util/SelectionManager"; import SharingManager from '../../util/SharingManager'; import { Transform } from "../../util/Transform"; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { CollectionViewType } from '../collections/CollectionBaseView'; +import { CollectionViewType } from '../collections/CollectionView'; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 7ec5d0471..cbb83b511 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -10,7 +10,7 @@ import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { DocumentManager } from "../../util/DocumentManager"; import { undoBatch } from "../../util/UndoManager"; -import { CollectionViewType } from "../collections/CollectionBaseView"; +import { CollectionViewType } from "../collections/CollectionView"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 5f758f496..7a1b4f9fb 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -12,7 +12,7 @@ import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { emptyFunction, returnFalse } from "../../../Utils"; import { DocumentType } from "../../documents/DocumentTypes"; import { Transform } from "../../util/Transform"; -import { CollectionViewType } from '../collections/CollectionBaseView'; +import { CollectionViewType } from '../collections/CollectionView'; import { CollectionSchemaPreview } from '../collections/CollectionSchemaView'; import { DocComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; @@ -51,7 +51,7 @@ export class PresElementBox extends DocComponent(P @computed get indexInPres() { return DocListCast(this.presentationDoc[this.Document.presBoxKey || ""]).indexOf(this.props.Document); } @computed get presentationDoc() { return Cast(this.Document.presBox, Doc) as Doc; } - @computed get targetDoc() { return this.Document.presentationTargetDoc as Doc } + @computed get targetDoc() { return this.Document.presentationTargetDoc as Doc; } @computed get currentIndex() { return NumCast(this.presentationDoc.selectedDoc); } /** diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 60307065a..f1d825aa0 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -15,7 +15,7 @@ import { LinkManager } from "../../util/LinkManager"; import { SearchUtil } from "../../util/SearchUtil"; import { Transform } from "../../util/Transform"; import { SEARCH_THUMBNAIL_SIZE } from "../../views/globalCssVariables.scss"; -import { CollectionViewType } from "../collections/CollectionBaseView"; +import { CollectionViewType } from "../collections/CollectionView"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { ContextMenu } from "../ContextMenu"; import { DocumentView } from "../nodes/DocumentView"; -- cgit v1.2.3-70-g09d2 From 43b09cb2aded66e67006d99e544d90637a62c048 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 23 Oct 2019 21:54:10 -0400 Subject: improved styling and colors of color picker on richtexteditor --- src/client/util/TooltipTextMenu.scss | 18 +++++++++ src/client/util/TooltipTextMenu.tsx | 75 +++++++++++++++++++++++++++--------- src/new_fields/SchemaHeaderField.ts | 11 ++++++ 3 files changed, 85 insertions(+), 19 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index f94799936..794998a01 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -182,6 +182,24 @@ } } +#colorPicker { + position: relative; + + svg { + width: 18px; + height: 18px; + } + + .buttonColor { + position: absolute; + top: 24px; + left: 1px; + width: 24px; + height: 4px; + margin-top: 0; + } +} + .strong, .heading { font-weight: bold; } .em { font-style: italic; } .underline {text-decoration: underline} diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 32a910497..92765b1e0 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -21,7 +21,7 @@ import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { updateBullets } from './ProsemirrorExampleTransfer'; import { DocumentDecorations } from '../views/DocumentDecorations'; import { SelectionManager } from './SelectionManager'; -import { PastelSchemaPalette } from '../../new_fields/SchemaHeaderField'; +import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField'; const { toggleMark, setBlockType } = require("prosemirror-commands"); const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); @@ -58,6 +58,8 @@ export class TooltipTextMenu { private fontSizeDom?: Node; private fontStyleDom?: Node; private listTypeBtnDom?: Node; + private colorDom?: Node; + private colorDropdownDom?: Node; // private _collapseBtn?: MenuItem; @@ -99,7 +101,7 @@ export class TooltipTextMenu { { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough", "Strikethrough") }, { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") }, { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") }, - { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') } + // { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') } ]; // add menu items @@ -135,8 +137,10 @@ export class TooltipTextMenu { }); // color menu - this.tooltip.appendChild(this.createColorTool().render(this.view).dom); - this.tooltip.appendChild(this.createColorDropdown().render(this.view).dom); + this.colorDom = this.createColorTool().render(this.view).dom; + this.colorDropdownDom = this.createColorDropdown().render(this.view).dom; + this.tooltip.appendChild(this.colorDom); + this.tooltip.appendChild(this.colorDropdownDom); // link menu this.updateLinkMenu(); @@ -760,14 +764,35 @@ export class TooltipTextMenu { return true; } + createHighlightTool() { + + } + createColorTool() { return new MenuItem({ - title: "Summarize", - label: "Summarize", - icon: icons.join, + title: "Color", + // label: "Color", + // icon: icon, css: "color:white;", class: "menuicon", execEvent: "", + render() { + let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("viewBox", "-100 -100 650 650"); + let path = document.createElementNS('http://www.w3.org/2000/svg', "path"); + path.setAttributeNS(null, "d", "M204.3 5C104.9 24.4 24.8 104.3 5.2 203.4c-37 187 131.7 326.4 258.8 306.7 41.2-6.4 61.4-54.6 42.5-91.7-23.1-45.4 9.9-98.4 60.9-98.4h79.7c35.8 0 64.8-29.6 64.9-65.3C511.5 97.1 368.1-26.9 204.3 5zM96 320c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm32-128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128-64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z"); + svg.appendChild(path); + + let color = document.createElement("div"); + color.className = "buttonColor"; + color.style.backgroundColor = TooltipTextMenuManager.Instance.color.toString(); + + let wrapper = document.createElement("div"); + wrapper.id = "colorPicker"; + wrapper.appendChild(svg); + wrapper.appendChild(color); + return wrapper; + }, run: (state, dispatch) => { TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch); } @@ -798,15 +823,15 @@ export class TooltipTextMenu { colorsWrapper.className = "colorPicker-wrapper"; let colors = [ - PastelSchemaPalette.get("pink2"), - PastelSchemaPalette.get("purple4"), - PastelSchemaPalette.get("bluegreen1"), - PastelSchemaPalette.get("yellow4"), - PastelSchemaPalette.get("red2"), - PastelSchemaPalette.get("bluegreen7"), - PastelSchemaPalette.get("bluegreen5"), - PastelSchemaPalette.get("orange1"), - "#f1efeb", + DarkPastelSchemaPalette.get("pink2"), + DarkPastelSchemaPalette.get("purple4"), + DarkPastelSchemaPalette.get("bluegreen1"), + DarkPastelSchemaPalette.get("yellow4"), + DarkPastelSchemaPalette.get("red2"), + DarkPastelSchemaPalette.get("bluegreen7"), + DarkPastelSchemaPalette.get("bluegreen5"), + DarkPastelSchemaPalette.get("orange1"), + "#757472", "#000" ]; @@ -815,7 +840,19 @@ export class TooltipTextMenu { button.className = color === TooltipTextMenuManager.Instance.color ? "colorPicker active" : "colorPicker"; if (color) { button.style.backgroundColor = color; - button.onclick = e => TooltipTextMenuManager.Instance.color = color; + button.onclick = e => { + TooltipTextMenuManager.Instance.color = color; + + TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch); + + // update color menu + let colorDom = self.createColorTool().render(self.view).dom; + let colorDropdownDom = self.createColorDropdown().render(self.view).dom; + self.colorDom && self.tooltip.replaceChild(colorDom, self.colorDom); + self.colorDropdownDom && self.tooltip.replaceChild(colorDropdownDom, self.colorDropdownDom); + self.colorDom = colorDom; + self.colorDropdownDom = colorDropdownDom; + }; } colorsWrapper.appendChild(button); }); @@ -1183,8 +1220,8 @@ export class TooltipTextMenu { //UPDATE LIST ITEM DROPDOWN // update link dropdown - let dropdown = await this.createLinkDropdown(); - let newLinkDropdowndom = dropdown.render(this.view).dom; + let linkDropdown = await this.createLinkDropdown(); + let newLinkDropdowndom = linkDropdown.render(this.view).dom; this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); this._linkDropdownDom = newLinkDropdowndom; diff --git a/src/new_fields/SchemaHeaderField.ts b/src/new_fields/SchemaHeaderField.ts index 92d0aec9a..42a8485ac 100644 --- a/src/new_fields/SchemaHeaderField.ts +++ b/src/new_fields/SchemaHeaderField.ts @@ -41,6 +41,17 @@ export const PastelSchemaPalette = new Map([ export const RandomPastel = () => Array.from(PastelSchemaPalette.values())[Math.floor(Math.random() * PastelSchemaPalette.size)]; +export const DarkPastelSchemaPalette = new Map([ + ["pink2", "#c932b0"], + ["purple4", "#913ad6"], + ["bluegreen1", "#3978ed"], + ["bluegreen7", "#2adb3e"], + ["bluegreen5", "#21b0eb"], + ["yellow4", "#edcc0c"], + ["red2", "#eb3636"], + ["orange1", "#f2740f"], +]); + @scriptingGlobal @Deserializable("schemaheader") export class SchemaHeaderField extends ObjectField { -- cgit v1.2.3-70-g09d2 From bd817ac118c53845c882b89c4b8a110dc8fe10c2 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 23 Oct 2019 22:22:34 -0400 Subject: added dropdown to highlighter tool to choose colors --- src/client/util/RichTextSchema.tsx | 17 +++++- src/client/util/TooltipTextMenu.tsx | 113 ++++++++++++++++++++++++++++++++++-- 2 files changed, 124 insertions(+), 6 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index e943b71e8..22756be24 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -321,8 +321,21 @@ export const marks: { [index: string]: MarkSpec } = { }], toDOM(node: any) { return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', { style: 'color: black' }]; - // ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : - // ["a", { ...node.attrs }, 0]; + } + }, + + highlight2: { + attrs: { + highlight: { default: "transparent" } + }, + inclusive: false, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + return { highlight: dom.getAttribute("backgroundColor") }; + } + }], + toDOM(node: any) { + return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }]; } }, diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 92765b1e0..0626eb504 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1,4 +1,4 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; +import { library, dom } from '@fortawesome/fontawesome-svg-core'; import { faListUl } from '@fortawesome/free-solid-svg-icons'; import { action, observable } from "mobx"; import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css @@ -60,6 +60,8 @@ export class TooltipTextMenu { private listTypeBtnDom?: Node; private colorDom?: Node; private colorDropdownDom?: Node; + private highlightDom?: Node; + private highlightDropdownDom?: Node; // private _collapseBtn?: MenuItem; @@ -136,6 +138,12 @@ export class TooltipTextMenu { }); + // highlight menu + this.highlightDom = this.createHighlightTool().render(this.view).dom; + this.highlightDropdownDom = this.createHighlightDropdown().render(this.view).dom; + this.tooltip.appendChild(this.highlightDom); + this.tooltip.appendChild(this.highlightDropdownDom); + // color menu this.colorDom = this.createColorTool().render(this.view).dom; this.colorDropdownDom = this.createColorDropdown().render(this.view).dom; @@ -765,14 +773,111 @@ export class TooltipTextMenu { } createHighlightTool() { + return new MenuItem({ + title: "Highlight", + css: "color:white;", + class: "menuicon", + execEvent: "", + render() { + let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("viewBox", "-100 -100 650 650"); + let path = document.createElementNS('http://www.w3.org/2000/svg', "path"); + path.setAttributeNS(null, "d", "M0 479.98L99.92 512l35.45-35.45-67.04-67.04L0 479.98zm124.61-240.01a36.592 36.592 0 0 0-10.79 38.1l13.05 42.83-50.93 50.94 96.23 96.23 50.86-50.86 42.74 13.08c13.73 4.2 28.65-.01 38.15-10.78l35.55-41.64-173.34-173.34-41.52 35.44zm403.31-160.7l-63.2-63.2c-20.49-20.49-53.38-21.52-75.12-2.35L190.55 183.68l169.77 169.78L530.27 154.4c19.18-21.74 18.15-54.63-2.35-75.13z"); + svg.appendChild(path); + + let color = document.createElement("div"); + color.className = "buttonColor"; + color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString() === "transparent" ? "#fff" : TooltipTextMenuManager.Instance.highlight.toString(); + + let wrapper = document.createElement("div"); + wrapper.id = "colorPicker"; + wrapper.appendChild(svg); + wrapper.appendChild(color); + return wrapper; + }, + run: (state, dispatch) => { + TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, this.view.state, this.view.dispatch); + } + }); + } + + public static insertHighlight(color: String, state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + + let highlightMark = state.schema.mark(state.schema.marks.highlight2, { highlight: color }); + dispatch(state.tr.addMark(state.selection.from, state.selection.to, highlightMark)); + } + + createHighlightDropdown() { + // menu item for color picker + let self = this; + let colors = new MenuItem({ + title: "", + execEvent: "", + class: "button-setting-disabled", + css: "", + render() { + let p = document.createElement("p"); + p.textContent = "Change highlight:"; + + let colorsWrapper = document.createElement("div"); + colorsWrapper.className = "colorPicker-wrapper"; + + let colors = [ + PastelSchemaPalette.get("pink2"), + PastelSchemaPalette.get("purple4"), + PastelSchemaPalette.get("bluegreen1"), + PastelSchemaPalette.get("yellow4"), + PastelSchemaPalette.get("red2"), + PastelSchemaPalette.get("bluegreen7"), + PastelSchemaPalette.get("bluegreen5"), + PastelSchemaPalette.get("orange1"), + // "#f1f0eb", + "transparent" + ]; + colors.forEach(color => { + let button = document.createElement("button"); + button.className = color === TooltipTextMenuManager.Instance.highlight ? "colorPicker active" : "colorPicker"; + if (color) { + button.style.backgroundColor = color === "transparent" ? "#fff" : color; + button.style.color = "black"; + button.textContent = color === "transparent" ? "X" : ""; + button.onclick = e => { + TooltipTextMenuManager.Instance.highlight = color; + + TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, self.view.state, self.view.dispatch); + + // update color menu + let highlightDom = self.createHighlightTool().render(self.view).dom; + let highlightDropdownDom = self.createHighlightDropdown().render(self.view).dom; + self.highlightDom && self.tooltip.replaceChild(highlightDom, self.highlightDom); + self.highlightDropdownDom && self.tooltip.replaceChild(highlightDropdownDom, self.highlightDropdownDom); + self.highlightDom = highlightDom; + self.highlightDropdownDom = highlightDropdownDom; + }; + } + colorsWrapper.appendChild(button); + }); + + let div = document.createElement("div"); + div.appendChild(p); + div.appendChild(colorsWrapper); + return div; + }, + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); + } + }); + + let colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem; + return colorDropdown; } createColorTool() { return new MenuItem({ title: "Color", - // label: "Color", - // icon: icon, css: "color:white;", class: "menuicon", execEvent: "", @@ -796,7 +901,6 @@ export class TooltipTextMenu { run: (state, dispatch) => { TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch); } - }); } @@ -1406,6 +1510,7 @@ class TooltipTextMenuManager { public _brushIsEmpty: boolean = true; public color: String = "#000"; + public highlight: String = "transparent"; public activeMenu: TooltipTextMenu | undefined; -- cgit v1.2.3-70-g09d2 From 66a15a7ac6a6907b398f142554f5d1a66988b630 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 23 Oct 2019 22:57:02 -0400 Subject: switched out text formatting buttons on rich text editor for fontawesome svg --- src/client/util/TooltipTextMenu.scss | 1 + src/client/util/TooltipTextMenu.tsx | 36 +++++++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 11 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 794998a01..16585d20b 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -188,6 +188,7 @@ svg { width: 18px; height: 18px; + margin-top: 11px; } .buttonColor { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 0626eb504..d32889dd0 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -95,14 +95,14 @@ export class TooltipTextMenu { this.tooltip = document.createElement("div"); this.tooltip.className = "tooltipMenu"; - // init buttons to the tooltip + // init buttons to the tooltip -- paths to svgs are obtained from fontawesome let items = [ - { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong", "Bold") }, - { command: toggleMark(schema.marks.em), dom: this.icon("i", "em", "Italic") }, - { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline", "Underline") }, - { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough", "Strikethrough") }, - { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") }, - { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") }, + { command: toggleMark(schema.marks.strong), dom: this.svgIcon("strong", "Bold", "M333.49 238a122 122 0 0 0 27-65.21C367.87 96.49 308 32 233.42 32H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h31.87v288H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h209.32c70.8 0 134.14-51.75 141-122.4 4.74-48.45-16.39-92.06-50.83-119.6zM145.66 112h87.76a48 48 0 0 1 0 96h-87.76zm87.76 288h-87.76V288h87.76a56 56 0 0 1 0 112z") }, + { command: toggleMark(schema.marks.em), dom: this.svgIcon("em", "Italic", "M320 48v32a16 16 0 0 1-16 16h-62.76l-80 320H208a16 16 0 0 1 16 16v32a16 16 0 0 1-16 16H16a16 16 0 0 1-16-16v-32a16 16 0 0 1 16-16h62.76l80-320H112a16 16 0 0 1-16-16V48a16 16 0 0 1 16-16h192a16 16 0 0 1 16 16z") }, + { command: toggleMark(schema.marks.underline), dom: this.svgIcon("underline", "Underline", "M32 64h32v160c0 88.22 71.78 160 160 160s160-71.78 160-160V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H272a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h32v160a80 80 0 0 1-160 0V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H32a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zm400 384H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z") }, + { command: toggleMark(schema.marks.strikethrough), dom: this.svgIcon("strikethrough", "Strikethrough", "M496 224H293.9l-87.17-26.83A43.55 43.55 0 0 1 219.55 112h66.79A49.89 49.89 0 0 1 331 139.58a16 16 0 0 0 21.46 7.15l42.94-21.47a16 16 0 0 0 7.16-21.46l-.53-1A128 128 0 0 0 287.51 32h-68a123.68 123.68 0 0 0-123 135.64c2 20.89 10.1 39.83 21.78 56.36H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h480a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm-180.24 96A43 43 0 0 1 336 356.45 43.59 43.59 0 0 1 292.45 400h-66.79A49.89 49.89 0 0 1 181 372.42a16 16 0 0 0-21.46-7.15l-42.94 21.47a16 16 0 0 0-7.16 21.46l.53 1A128 128 0 0 0 224.49 480h68a123.68 123.68 0 0 0 123-135.64 114.25 114.25 0 0 0-5.34-24.36z") }, + { command: toggleMark(schema.marks.superscript), dom: this.svgIcon("superscript", "Superscript", "M496 160h-16V16a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 64h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") }, + { command: toggleMark(schema.marks.subscript), dom: this.svgIcon("subscript", "Subscript", "M496 448h-16V304a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 352h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") }, // { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') } ]; @@ -787,7 +787,7 @@ export class TooltipTextMenu { let color = document.createElement("div"); color.className = "buttonColor"; - color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString() === "transparent" ? "#fff" : TooltipTextMenuManager.Instance.highlight.toString(); + color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString(); let wrapper = document.createElement("div"); wrapper.id = "colorPicker"; @@ -832,7 +832,7 @@ export class TooltipTextMenu { PastelSchemaPalette.get("bluegreen7"), PastelSchemaPalette.get("bluegreen5"), PastelSchemaPalette.get("orange1"), - // "#f1f0eb", + "white", "transparent" ]; @@ -840,8 +840,7 @@ export class TooltipTextMenu { let button = document.createElement("button"); button.className = color === TooltipTextMenuManager.Instance.highlight ? "colorPicker active" : "colorPicker"; if (color) { - button.style.backgroundColor = color === "transparent" ? "#fff" : color; - button.style.color = "black"; + button.style.backgroundColor = color; button.textContent = color === "transparent" ? "X" : ""; button.onclick = e => { TooltipTextMenuManager.Instance.highlight = color; @@ -1239,6 +1238,21 @@ export class TooltipTextMenu { return span; } + svgIcon(name: string, title: string = name, dpath: string) { + let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("viewBox", "-100 -100 650 650"); + let path = document.createElementNS('http://www.w3.org/2000/svg', "path"); + path.setAttributeNS(null, "d", dpath); + svg.appendChild(path); + + let span = document.createElement("span"); + span.className = name + " menuicon"; + span.title = title; + span.appendChild(svg); + + return span; + } + //method for checking whether node can be inserted canInsert(state: EditorState, nodeType: NodeType>) { let $from = state.selection.$from; -- cgit v1.2.3-70-g09d2 From 910e4d8a22a312d2ca0b8a970ff434604f7c6f91 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 23 Oct 2019 23:35:01 -0400 Subject: several fixes to audio documents to make linking work better and to customize the audio controls UI --- src/client/documents/Documents.ts | 3 +- src/client/util/DocumentManager.ts | 4 +- src/client/views/nodes/AudioBox.scss | 62 +++++++++++++++++++--- src/client/views/nodes/AudioBox.tsx | 94 +++++++++++++++++++++------------ src/client/views/nodes/DocuLinkBox.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 16 ++++-- src/new_fields/Doc.ts | 2 + 7 files changed, 134 insertions(+), 51 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index cad417d33..f26594e04 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -171,7 +171,7 @@ export namespace Docs { }], [DocumentType.AUDIO, { layout: { view: AudioBox, dataField: data }, - options: { height: 32 } + options: { height: 35, backgroundColor: "lightGray", borderRounding: "20%" } }], [DocumentType.PDF, { layout: { view: PDFBox, dataField: data }, @@ -691,6 +691,7 @@ export namespace DocUtils { } }); } + export function MakeLink(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, title: string = "", description: string = "", id?: string) { let sv = DocumentManager.Instance.getDocumentView(source.doc); if (sv && sv.props.ContainingCollectionDoc === target.doc) return; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 0f2a47dd0..346e88f40 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -101,10 +101,10 @@ export class DocumentManager { @computed public get LinkedDocumentViews() { let pairs = DocumentManager.Instance.DocumentViews.filter(dv => - dv.isSelected() || Doc.IsBrushed(dv.props.Document) // draw links from DocumentViews that are selected or brushed OR + (dv.isSelected() || Doc.IsBrushed(dv.props.Document)) // draw links from DocumentViews that are selected or brushed OR || DocumentManager.Instance.DocumentViews.some(dv2 => { // Documentviews which let rest = DocListCast(dv2.props.Document.links).some(l => Doc.AreProtosEqual(l, dv.props.Document));// are link doc anchors - let init = dv2.isSelected() || Doc.IsBrushed(dv2.props.Document); // on a view that is selected or brushed + let init = (dv2.isSelected() || Doc.IsBrushed(dv2.props.Document)) && dv2.Document.type !== DocumentType.AUDIO; // on a view that is selected or brushed return init && rest; }) ).reduce((pairs, dv) => { diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index 14b90c570..3d6f6c4f9 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -1,10 +1,10 @@ .audiobox-container, .audiobox-container-interactive { width: 100%; height: 100%; - min-height: 32px; position: inherit; display:flex; pointer-events: all; + cursor:default; .audiobox-handle { width:20px; height:100%; @@ -35,18 +35,66 @@ width:100%; height:100%; position: relative; - display: grid; + display: flex; .audiobox-player { margin-top:auto; margin-bottom:auto; width:100%; + height: 80%; position: relative; - display: grid; - .audiobox-marker { - position:absolute; - width:10px; + display: flex; + .audiobox-playhead { + position: relative; + margin-top: auto; + margin-bottom: auto; + width: 25px; + } + .audiobox-timeline { + position:relative; height:100%; - background:green; + width:100%; + .audiobox-current { + width: 1px; + height:100%; + background-color: red; + position: absolute;; + } + .audiobox-linker { + position:absolute; + width:15px; + min-height:10px; + height:15px; + margin-left:-2.55px; + background:gray; + border-radius: 100%; + background-color: transparent; + box-shadow: black 2px 2px 1px; + .docuLinkBox-cont { + width:15px !important; + height:15px !important; + left: calc(100% - 15px) !important; + top: calc(100% - 15px) !important; + } + } + .audiobox-linker:hover { + transform:scale(1.5); + } + .audiobox-marker-container { + position:absolute; + width:10px; + height:100%; + background:gray; + border-radius: 5px; + box-shadow: black 2px 2px 1px; + .audiobox-marker { + position:relative; + height: calc(100% - 15px); + margin-top: 15px; + } + .audio-marker:hover { + border: orange 2px solid; + } + } } } } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 57360272e..3933c6257 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -7,17 +7,18 @@ import { AudioField, nullAudio } from "../../../new_fields/URLField"; import { DocExtendableComponent } from "../DocComponent"; import { makeInterface, createSchema } from "../../../new_fields/Schema"; import { documentSchema } from "../../../new_fields/documentSchemas"; -import { Utils } from "../../../Utils"; +import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent } from "../../../Utils"; import { RouteStore } from "../../../server/RouteStore"; import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx"; import { DateField } from "../../../new_fields/DateField"; import { SelectionManager } from "../../util/SelectionManager"; -import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { Doc, DocListCast, WidthSym } from "../../../new_fields/Doc"; import { ContextMenuProps } from "../ContextMenuItem"; import { ContextMenu } from "../ContextMenu"; import { Id } from "../../../new_fields/FieldSymbols"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { DocumentManager } from "../../util/DocumentManager"; +import { DocumentView } from "./DocumentView"; interface Window { MediaRecorder: MediaRecorder; @@ -44,16 +45,14 @@ export class AudioBox extends DocExtendableComponent AudioBox._scrubTime = timeInMillisFrom1970); public static ActiveRecordings: Doc[] = []; componentDidMount() { - runInAction(() => this._duration = NumCast(this.dataDoc.duration, 1)); runInAction(() => this._audioState = this.path ? "recorded" : "unrecorded"); this._linkPlayDisposer = reaction(() => this.layoutDoc.scrollToLinkID, scrollLinkId => { @@ -75,14 +74,12 @@ export class AudioBox extends DocExtendableComponent { + timecodeChanged = () => { const extensionDoc = this.extensionDoc; const htmlEle = this._ele; const start = extensionDoc && DateCast(extensionDoc.recordingStart); - if (htmlEle && !htmlEle.paused && start) { - htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = this._duration = htmlEle.duration); - setTimeout(this.updateHighlights, 30); - this.Document.currentTimecode = htmlEle.currentTime; + if (start && htmlEle) { + htmlEle && htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = htmlEle.duration); DocListCast(this.dataDoc.links).map(l => { let la1 = l.anchor1 as Doc; let linkTime = NumCast(l.anchor2Timecode); @@ -90,25 +87,29 @@ export class AudioBox extends DocExtendableComponent this._lastUpdate && linkTime < htmlEle.currentTime) { + if (linkTime > NumCast(this.Document.currentTimecode) && linkTime < htmlEle.currentTime) { Doc.linkFollowHighlight(la1); } }); - this._lastUpdate = htmlEle.currentTime; + this.Document.currentTimecode = htmlEle.currentTime; } } + pause = action(() => { + this._ele!.pause(); + this._playing = false; + }) + playFrom = (seekTimeInSeconds: number) => { if (this._ele) { if (seekTimeInSeconds < 0) { - this._ele.pause(); + this.pause(); } else if (seekTimeInSeconds <= this._ele.duration) { this._ele.currentTime = seekTimeInSeconds; this._ele.play(); - this._lastUpdate = seekTimeInSeconds; - setTimeout(this.updateHighlights, 0); + runInAction(() => this._playing = true); } else { - this._ele.pause(); + this.pause(); } } } @@ -170,6 +171,7 @@ export class AudioBox extends DocExtendableComponent { this._recorder.stop(); + this.dataDoc.duration = (new Date().getTime() - this._recordStart) / 1000; this._audioState = "recorded"; let ind = AudioBox.ActiveRecordings.indexOf(this.props.Document); ind !== -1 && (AudioBox.ActiveRecordings.splice(ind, 1)); @@ -182,12 +184,15 @@ export class AudioBox extends DocExtendableComponent setTimeout(this.updateHighlights, 30); onPlay = (e: any) => this.playFrom(this._ele!.paused ? this._ele!.currentTime : -1); + onStop = (e: any) => { + this.pause(); + this._ele!.currentTime = 0; + e.stopPropagation(); + } setRef = (e: HTMLAudioElement | null) => { - e && e.addEventListener("play", this.playClick as any); - e && e.addEventListener("timeupdate", () => this.props.Document.currentTimecode = e!.currentTime); + e && e.addEventListener("timeupdate", action(() => this.Document.currentTimecode = e!.currentTime)); this._ele = e; } @@ -216,21 +221,40 @@ export class AudioBox extends DocExtendableComponent :
- - {DocListCast(this.dataDoc.links).map((l, i) => { - let la1 = l.anchor1 as Doc; - let la2 = l.anchor2 as Doc; - let linkTime = NumCast(l.anchor2Timecode); - if (Doc.AreProtosEqual(la1, this.dataDoc)) { - la1 = l.anchor2 as Doc; - la2 = l.anchor1 as Doc; - linkTime = NumCast(l.anchor1Timecode); - } - return
- DocumentManager.Instance.FollowLink(l, la2, document => this.props.addDocTab(document, undefined, "onRight"), false)} - style={{ left: `${linkTime / this._duration * 100}%` }} />; - })} - {this.audio} + +
+
e.stopPropagation()} + onPointerDown={e => { + if (e.button === 0 && !e.ctrlKey) { + let rect = (e.target as any).getBoundingClientRect(); + this._ele!.currentTime = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration); + this.pause(); + e.stopPropagation(); + } + }} > + {DocListCast(this.dataDoc.links).map((l, i) => { + let la1 = l.anchor1 as Doc; + let la2 = l.anchor2 as Doc; + let linkTime = NumCast(l.anchor2Timecode); + if (Doc.AreProtosEqual(la1, this.dataDoc)) { + la1 = l.anchor2 as Doc; + la2 = l.anchor1 as Doc; + linkTime = NumCast(l.anchor1Timecode); + } + return !linkTime ? (null) :
+
+ +
+
Doc.linkFollowHighlight(la1)} + onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(linkTime); e.stopPropagation(); } }} + onClick={e => { if (e.button === 0 && !e.ctrlKey) { this.pause(); e.stopPropagation(); } }} /> +
; + })} +
+ {this.audio} +
} diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index 7119b0db0..f2ab6fcd8 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -33,7 +33,7 @@ export class DocuLinkBox extends DocComponent(Doc document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); document.addEventListener("pointerup", this.onPointerUp); - e.stopPropagation(); + (e.button === 0 && !e.ctrlKey) && e.stopPropagation(); } onPointerMove = action((e: PointerEvent) => { let cdiv = this._ref.current!.parentElement; @@ -43,7 +43,7 @@ export class DocuLinkBox extends DocComponent(Doc let separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY)); let dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy)); if (separation > 100) { - DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], this.props.ContainingCollectionDoc as Doc, this.props.Document); // Containging collection is the document, not a collection... hack. + DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, this.props.Document); // Containging collection is the document, not a collection... hack. document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); } else if (dragdist > separation) { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c82fd0f0f..ed93aa83e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -41,6 +41,7 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); +import { link } from 'fs'; library.add(fa.faEdit); library.add(fa.faTrash); @@ -585,8 +586,16 @@ export class DocumentView extends DocComponent(Docu layoutKey={this.props.layoutKey || "layout"} DataDoc={this.props.DataDoc} />); } - linkEndpoint = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; - linkEndpointDoc = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? Cast(linkDoc.anchor1, Doc) as Doc : Cast(linkDoc.anchor2, Doc) as Doc; + linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document); + + // used to decide whether a link document should be created or not. + // if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here. + // would be good to generalize this some way. + isNonTemporalLink = (linkDoc: Doc) => { + let anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc; + let ept = Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1Timecode : linkDoc.anchor2Timecode; + return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true; + } render() { if (!this.props.Document) return (null); @@ -658,8 +667,7 @@ export class DocumentView extends DocComponent(Docu onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)} > - {this.Document.links && DocListCast(this.Document.links).map((d, i) => - //this.linkEndpointDoc(d).type === DocumentType.PDFANNO ? (null) : + {this.Document.links && DocListCast(this.Document.links).filter(this.isNonTemporalLink).map((d, i) =>
)} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 08cb66d5f..6aad4a6be 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -669,6 +669,8 @@ export namespace Doc { return doc; } + export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; } + export function linkFollowUnhighlight() { Doc.UnhighlightAll(); document.removeEventListener("pointerdown", linkFollowUnhighlight); -- cgit v1.2.3-70-g09d2 From 45074de50007e0693df8835643464da962e62620 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 24 Oct 2019 10:43:49 -0400 Subject: fixed width of rich text editor --- src/client/util/TooltipTextMenu.scss | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 16585d20b..1f619b4f5 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -101,8 +101,9 @@ transform: translateY(-85px); pointer-events: all; height: 35px; - width: 650px; + // width: 650px; padding: 3px; + padding-bottom: 5px; box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); display: flex; align-items: center; @@ -122,10 +123,6 @@ } } -#link-drag { - background-color: #323232; -} - .menuicon { width: 25px; height: 25px; @@ -188,7 +185,7 @@ svg { width: 18px; height: 18px; - margin-top: 11px; + // margin-top: 11px; } .buttonColor { @@ -201,12 +198,14 @@ } } - .strong, .heading { font-weight: bold; } - .em { font-style: italic; } - .underline {text-decoration: underline} - .superscript {vertical-align:super} - .subscript { vertical-align:sub } - .strikethrough {text-decoration-line:line-through} +#link-drag { + background-color: #323232; +} + +.underline svg { + margin-top: 15px; +} + .font-size-indicator { font-size: 12px; padding-right: 0px; @@ -299,8 +298,9 @@ left: -27px; top: 31px; background-color: #323232; + border: 1px solid #4d4d4d; color: $light-color-secondary; - border: none; + // border: none; // border: 1px solid $light-color-secondary; border-radius: 0 6px 6px 6px; padding: 3px; -- cgit v1.2.3-70-g09d2 From b013be665b704cb6241979f1bf53403a4b6bc1a3 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 5 Nov 2019 16:56:04 -0500 Subject: fixes for batched array --- src/client/util/Import & Export/DirectoryImportBox.tsx | 6 +++--- src/server/apis/google/GooglePhotosUploadUtils.ts | 17 +++++++++-------- src/server/index.ts | 15 +++++++-------- 3 files changed, 19 insertions(+), 19 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index f27d05487..5904088fc 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -108,7 +108,8 @@ export default class DirectoryImportBox extends React.Component runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`); - const uploads = await BatchedArray.from(validated, { batchSize: 15 }).batchedMapAsync(async batch => { + const batched = BatchedArray.from(validated, { batchSize: 15 }); + const uploads = await batched.batchedMapAsync(async (batch, collector) => { const formData = new FormData(); batch.forEach(file => { @@ -117,9 +118,8 @@ export default class DirectoryImportBox extends React.Component formData.append(Utils.GenerateGuid(), file); }); - const responses = await Identified.PostFormDataToServer(RouteStore.upload, formData); + collector.push(...(await Identified.PostFormDataToServer(RouteStore.upload, formData))); runInAction(() => this.completed += batch.length); - return responses as ImageUploadResponse[]; }); await Promise.all(uploads.map(async upload => { diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts index 4a67e57cc..36256822c 100644 --- a/src/server/apis/google/GooglePhotosUploadUtils.ts +++ b/src/server/apis/google/GooglePhotosUploadUtils.ts @@ -1,7 +1,8 @@ + import request = require('request-promise'); import { GoogleApiServerUtils } from './GoogleApiServerUtils'; import * as path from 'path'; -import { MediaItemCreationResult } from './SharedTypes'; +import { MediaItemCreationResult, NewMediaItemResult } from './SharedTypes'; import { NewMediaItem } from "../../index"; import { BatchedArray, TimeUnit } from 'array-batcher'; import { DashUploadUtils } from '../../DashUploadUtils'; @@ -56,10 +57,11 @@ export namespace GooglePhotosUploadUtils { })); }; - export const CreateMediaItems = async (newMediaItems: NewMediaItem[], album?: { id: string }): Promise => { - const newMediaItemResults = await BatchedArray.from(newMediaItems, { batchSize: 50 }).batchedMapPatientInterval( + export const CreateMediaItems = async (newMediaItems: NewMediaItem[], album?: { id: string }): Promise => { + const batched = BatchedArray.from(newMediaItems, { batchSize: 50 }); + return batched.batchedMapPatientInterval( { magnitude: 100, unit: TimeUnit.Milliseconds }, - async (batch: NewMediaItem[]) => { + async (batch, collector) => { const parameters = { method: 'POST', headers: headers('json'), @@ -68,18 +70,17 @@ export namespace GooglePhotosUploadUtils { json: true }; album && (parameters.body.albumId = album.id); - return (await new Promise((resolve, reject) => { + collector.push(...(await new Promise((resolve, reject) => { request(parameters, (error, _response, body) => { if (error) { reject(error); } else { - resolve(body); + resolve(body.newMediaItemResults); } }); - })).newMediaItemResults; + }))); } ); - return { newMediaItemResults }; }; } \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index ee6a497ba..89b5085b1 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1049,23 +1049,22 @@ addSecureRoute({ let failed: number[] = []; - const newMediaItems = await BatchedArray.from(media, { batchSize: 25 }).batchedMapPatientInterval( + const batched = BatchedArray.from(media, { batchSize: 25 }); + const newMediaItems = await batched.batchedMapPatientInterval( { magnitude: 100, unit: TimeUnit.Milliseconds }, - async (batch: GooglePhotosUploadUtils.MediaInput[]) => { - const newMediaItems: NewMediaItem[] = []; + async (batch, collector) => { for (let index = 0; index < batch.length; index++) { - const element = batch[index]; - const uploadToken = await GooglePhotosUploadUtils.DispatchGooglePhotosUpload(element.url); + const { url, description } = batch[index]; + const uploadToken = await GooglePhotosUploadUtils.DispatchGooglePhotosUpload(url); if (!uploadToken) { failed.push(index); } else { - newMediaItems.push({ - description: element.description, + collector.push({ + description, simpleMediaItem: { uploadToken } }); } } - return newMediaItems; } ); -- cgit v1.2.3-70-g09d2 From b625719e71bcfc09e905b8f164b3ed76994b1d59 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 10 Nov 2019 19:16:21 -0500 Subject: fixed changing bullet type --- src/client/util/TooltipTextMenu.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 31d98887f..b1bd73e54 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -531,8 +531,9 @@ export class TooltipTextMenu { } //remove all node typeand apply the passed-in one to the selected text - changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => { + changeToNodeType = (nodeType: NodeType | undefined) => { //remove oldif (nodeType) { //add new + let view = this.view; if (nodeType === schema.nodes.bullet_list) { wrapInList(nodeType)(view.state, view.dispatch); } else { -- cgit v1.2.3-70-g09d2 From f4ca517725714d770096d683478a6f5cc995c0c3 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 10 Nov 2019 19:46:32 -0500 Subject: fixed switching between ordered list and bullets. --- src/client/util/RichTextSchema.tsx | 3 ++- src/client/util/TooltipTextMenu.tsx | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index cc3548e1a..a224c57eb 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -241,6 +241,7 @@ export const nodes: { [index: string]: NodeSpec } = { }, toDOM(node: Node) { const bs = node.attrs.bulletStyle; + if (node.attrs.mapStyle === "bullet") return ['ul', 0]; const decMap = bs ? "decimal" + bs : ""; const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : ""; let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap; @@ -273,7 +274,7 @@ export const nodes: { [index: string]: NodeSpec } = { const bs = node.attrs.bulletStyle; const decMap = bs ? "decimal" + bs : ""; const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : ""; - let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap; + let map = node.attrs.mapStyle === "decimal" ? decMap : node.attrs.mapStyle === "multi" ? multiMap : ""; return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."]; //return ["li", { class: `${map}` }, 0]; } diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index b1bd73e54..5cbf8b761 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -171,7 +171,8 @@ export class TooltipTextMenu { //list types this.listTypeToIcon = new Map(); - this.listTypeToIcon.set(schema.nodes.bullet_list, ":"); + //this.listTypeToIcon.set(schema.nodes.bullet_list, ":"); + this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "bullet" }), ":"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A"); // this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜"); @@ -539,14 +540,14 @@ export class TooltipTextMenu { } else { var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks()); if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { - let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle); + let tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); view.dispatch(tx2); })) { let tx2 = view.state.tr; - let tx3 = nodeType ? updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle) : tx2; + let tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); -- cgit v1.2.3-70-g09d2 From d220b23867050fb6565fe639c3be28080f589aa2 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 11 Nov 2019 18:39:23 -0500 Subject: made font size and font style text marks be parametrized to simplify api. --- src/client/util/RichTextSchema.tsx | 154 ++-------------- src/client/util/TooltipTextMenu.tsx | 277 +++++++++++++++++----------- src/client/views/nodes/FormattedTextBox.tsx | 15 +- 3 files changed, 190 insertions(+), 256 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index a224c57eb..a5e0ca720 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -483,72 +483,26 @@ export const marks: { [index: string]: MarkSpec } = { toDOM() { return codeDOM; } }, - // pFontFamily: { - // attrs: { - // style: { default: 'font-family: "Times New Roman", Times, serif;' }, - // }, - // parseDOM: [{ - // tag: "span", getAttrs(dom: any) { - // if (getComputedStyle(dom).font === "Times New Roman") return { style: `font-family: "Times New Roman", Times, serif;` }; - // if (getComputedStyle(dom).font === "Arial, Helvetica") return { style: `font-family: Arial, Helvetica, sans-serif;` }; - // if (getComputedStyle(dom).font === "Georgia") return { style: `font-family: Georgia, serif;` }; - // if (getComputedStyle(dom).font === "Comic Sans") return { style: `font-family: "Comic Sans MS", cursive, sans-serif;` }; - // if (getComputedStyle(dom).font === "Tahoma, Geneva") return { style: `font-family: Tahoma, Geneva, sans-serif;` }; - // } - // }], - // toDOM: (node: any) => ['span', { - // style: node.attrs.style - // }] - // }, - - /* FONTS */ - timesNewRoman: { - parseDOM: [{ style: 'font-family: "Times New Roman", Times, serif;' }], - toDOM: () => ['span', { - style: 'font-family: "Times New Roman", Times, serif;' - }] - }, - - arial: { - parseDOM: [{ style: 'font-family: Arial, Helvetica, sans-serif;' }], - toDOM: () => ['span', { - style: 'font-family: Arial, Helvetica, sans-serif;' - }] - }, - - georgia: { - parseDOM: [{ style: 'font-family: Georgia, serif;' }], - toDOM: () => ['span', { - style: 'font-family: Georgia, serif;' - }] - }, - - comicSans: { - parseDOM: [{ style: 'font-family: "Comic Sans MS", cursive, sans-serif;' }], - toDOM: () => ['span', { - style: 'font-family: "Comic Sans MS", cursive, sans-serif;' - }] - }, - - tahoma: { - parseDOM: [{ style: 'font-family: Tahoma, Geneva, sans-serif;' }], - toDOM: () => ['span', { - style: 'font-family: Tahoma, Geneva, sans-serif;' - }] - }, - - impact: { - parseDOM: [{ style: 'font-family: Impact, Charcoal, sans-serif;' }], - toDOM: () => ['span', { - style: 'font-family: Impact, Charcoal, sans-serif;' - }] - }, - - crimson: { - parseDOM: [{ style: 'font-family: "Crimson Text", sans-serif;' }], - toDOM: () => ['span', { - style: 'font-family: "Crimson Text", sans-serif;' + pFontFamily: { + attrs: { + family: { default: "Crimson Text" }, + }, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + let cstyle = getComputedStyle(dom); + if (cstyle.font) { + if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" }; + if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" }; + if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" }; + if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" }; + if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" }; + if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" }; + } + } + }], + toDOM: (node) => ['span', { + style: `font-family: "${node.attrs.family}";` }] }, @@ -574,76 +528,6 @@ export const marks: { [index: string]: MarkSpec } = { style: `font-size: ${node.attrs.fontSize}px;` }] }, - - p10: { - parseDOM: [{ style: 'font-size: 10px;' }], - toDOM: () => ['span', { - style: 'font-size: 10px;' - }] - }, - - p12: { - parseDOM: [{ style: 'font-size: 12px;' }], - toDOM: () => ['span', { - style: 'font-size: 12px;' - }] - }, - - p14: { - parseDOM: [{ style: 'font-size: 14px;' }], - toDOM: () => ['span', { - style: 'font-size: 14px;' - }] - }, - - p16: { - parseDOM: [{ style: 'font-size: 16px;' }], - toDOM: () => ['span', { - style: 'font-size: 16px;' - }] - }, - - p18: { - parseDOM: [{ style: 'font-size: 18px;' }], - toDOM: () => ['span', { - style: 'font-size: 18px;' - }] - }, - - p20: { - parseDOM: [{ style: 'font-size: 20px;' }], - toDOM: () => ['span', { - style: 'font-size: 20px;' - }] - }, - - p24: { - parseDOM: [{ style: 'font-size: 24px;' }], - toDOM: () => ['span', { - style: 'font-size: 24px;' - }] - }, - - p32: { - parseDOM: [{ style: 'font-size: 32px;' }], - toDOM: () => ['span', { - style: 'font-size: 32px;' - }] - }, - - p48: { - parseDOM: [{ style: 'font-size: 48px;' }], - toDOM: () => ['span', { - style: 'font-size: 48px;' - }] - }, - - p72: { - parseDOM: [{ style: 'font-size: 72px;' }], - toDOM: () => ['span', { - style: 'font-size: 72px;' - }] - }, }; export class ImageResizeView { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 5cbf8b761..9ce7acec8 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -29,11 +29,9 @@ export class TooltipTextMenu { public tooltip: HTMLElement; private view: EditorView; private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; - private fontStyles: MarkType[]; - private fontSizes: MarkType[]; private listTypes: (NodeType | any)[]; - private fontSizeToNum: Map; - private fontStylesToName: Map; + private fontSizes: Mark[] = []; + private fontStyles: Mark[] = []; private listTypeToIcon: Map; //private link: HTMLAnchorElement; private wrapper: HTMLDivElement; @@ -135,46 +133,32 @@ export class TooltipTextMenu { //list of font styles - this.fontStylesToName = new Map(); - this.fontStylesToName.set(schema.marks.timesNewRoman, "Times New Roman"); - this.fontStylesToName.set(schema.marks.arial, "Arial"); - this.fontStylesToName.set(schema.marks.georgia, "Georgia"); - this.fontStylesToName.set(schema.marks.comicSans, "Comic Sans MS"); - this.fontStylesToName.set(schema.marks.tahoma, "Tahoma"); - this.fontStylesToName.set(schema.marks.impact, "Impact"); - this.fontStylesToName.set(schema.marks.crimson, "Crimson Text"); - this.fontStyles = Array.from(this.fontStylesToName.keys()); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Times New Roman" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Arial" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Georgia" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Comic Sans MS" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Tahoma" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Impact" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Crimson Text" })); //font sizes - this.fontSizeToNum = new Map(); - this.fontSizeToNum.set(schema.marks.p10, 10); - this.fontSizeToNum.set(schema.marks.p12, 12); - this.fontSizeToNum.set(schema.marks.p14, 14); - this.fontSizeToNum.set(schema.marks.p16, 16); - this.fontSizeToNum.set(schema.marks.p18, 18); - this.fontSizeToNum.set(schema.marks.p20, 20); - this.fontSizeToNum.set(schema.marks.p24, 24); - this.fontSizeToNum.set(schema.marks.p32, 32); - this.fontSizeToNum.set(schema.marks.p48, 48); - this.fontSizeToNum.set(schema.marks.p72, 72); - this.fontSizeToNum.set(schema.marks.pFontSize, 10); - // this.fontSizeToNum.set(schema.marks.pFontSize, 12); - // this.fontSizeToNum.set(schema.marks.pFontSize, 14); - // this.fontSizeToNum.set(schema.marks.pFontSize, 16); - // this.fontSizeToNum.set(schema.marks.pFontSize, 18); - // this.fontSizeToNum.set(schema.marks.pFontSize, 20); - // this.fontSizeToNum.set(schema.marks.pFontSize, 24); - // this.fontSizeToNum.set(schema.marks.pFontSize, 32); - // this.fontSizeToNum.set(schema.marks.pFontSize, 48); - // this.fontSizeToNum.set(schema.marks.pFontSize, 72); - this.fontSizes = Array.from(this.fontSizeToNum.keys()); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 10 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 12 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 14 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 16 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 18 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 20 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 24 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 32 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 48 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 72 })); //list types this.listTypeToIcon = new Map(); //this.listTypeToIcon.set(schema.nodes.bullet_list, ":"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "bullet" }), ":"); - this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1"); - this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A"); + this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1)"); + this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A)"); // this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜"); this.listTypes = Array.from(this.listTypeToIcon.keys()); @@ -195,16 +179,13 @@ export class TooltipTextMenu { //label of dropdown will change to given label updateFontSizeDropdown(label: string) { - //filtering function - might be unecessary - let cut = (arr: MenuItem[]) => arr.filter(x => x); - //font SIZES let fontSizeBtns: MenuItem[] = []; - this.fontSizeToNum.forEach((number, mark) => { - fontSizeBtns.push(this.dropdownMarkBtn(String(number), "color: black; width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes)); + this.fontSizes.forEach(mark => { + fontSizeBtns.push(this.dropdownFontSizeBtn(String(mark.attrs.fontSize), "color: black; width: 50px;", mark, this.view, this.changeToFontSize)); }); - let newfontSizeDom = (new Dropdown(cut(fontSizeBtns), { + let newfontSizeDom = (new Dropdown(fontSizeBtns, { label: label, css: "color:black; min-width: 60px; padding-left: 5px; margin-right: 0;" }) as MenuItem).render(this.view).dom; @@ -215,20 +196,15 @@ export class TooltipTextMenu { this.fontSizeDom = newfontSizeDom; } - // Make the DIV element draggable - //label of dropdown will change to given label updateFontStyleDropdown(label: string) { - //filtering function - might be unecessary - let cut = (arr: MenuItem[]) => arr.filter(x => x); - //font STYLES let fontBtns: MenuItem[] = []; - this.fontStylesToName.forEach((name, mark) => { - fontBtns.push(this.dropdownMarkBtn(name, "color: black; font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles)); + this.fontStyles.forEach((mark) => { + fontBtns.push(this.dropdownFontFamilyBtn(mark.attrs.family, "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;", mark, this.view, this.changeToFontFamily)); }); - let newfontStyleDom = (new Dropdown(cut(fontBtns), { + let newfontStyleDom = (new Dropdown(fontBtns, { label: label, css: "color:black; width: 125px; margin-left: -3px; padding-left: 2px;" }) as MenuItem).render(this.view).dom; @@ -468,8 +444,7 @@ export class TooltipTextMenu { this.tooltip.appendChild(listTypeBtn); return listTypeBtn; } - - //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text + //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected textchangeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => { changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => { let { $cursor, ranges } = view.state.selection as TextSelection; let state = view.state; @@ -498,29 +473,6 @@ export class TooltipTextMenu { }); if (markType) { - // fontsize - if (markType.name[0] === 'p') { - let size = this.fontSizeToNum.get(markType); - if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } - if (this.editorProps) { - let ruleProvider = this.editorProps.ruleProvider; - let heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleSize_" + heading] = size; - } - } - } - else { - let fontName = this.fontStylesToName.get(markType); - if (fontName) { this.updateFontStyleDropdown(fontName); } - if (this.editorProps) { - let ruleProvider = this.editorProps.ruleProvider; - let heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleFont_" + heading] = fontName; - } - } - } //actually apply font if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, @@ -531,6 +483,90 @@ export class TooltipTextMenu { } } + changeToFontFamily = (mark: Mark, view: EditorView) => { + let { $cursor, ranges } = view.state.selection as TextSelection; + let state = view.state; + let dispatch = view.dispatch; + + //remove all other active font marks + if ($cursor) { + if (view.state.schema.marks.pFontFamily.isInSet(state.storedMarks || $cursor.marks())) { + dispatch(state.tr.removeStoredMark(view.state.schema.marks.pFontFamily)); + } + } else { + let has = false; + for (let i = 0; !has && i < ranges.length; i++) { + let { $from, $to } = ranges[i]; + has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontFamily); + } + for (let i of ranges) { + if (has) { + toggleMark(view.state.schema.marks.pFontFamily)(view.state, view.dispatch, view); + } + } + } + + let fontName = mark.attrs.family; + if (fontName) { this.updateFontStyleDropdown(fontName); } + if (this.editorProps) { + let ruleProvider = this.editorProps.ruleProvider; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleFont_" + heading] = fontName; + } + } + //actually apply font + if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { + let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, + { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: fontName }), view.state.schema); + view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); + } + else view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, view.state.schema.marks.pFontFamily.create({ family: fontName }))); + view.state.storedMarks = [...(view.state.storedMarks || []), view.state.schema.marks.pFontFamily.create({ family: fontName })]; + } + + changeToFontSize = (mark: Mark, view: EditorView) => { + let { $cursor, ranges } = view.state.selection as TextSelection; + let state = view.state; + let dispatch = view.dispatch; + + //remove all other active font marks + if ($cursor) { + if (view.state.schema.marks.pFontSize.isInSet(state.storedMarks || $cursor.marks())) { + dispatch(state.tr.removeStoredMark(view.state.schema.marks.pFontSize)); + } + } else { + let has = false; + for (let i = 0; !has && i < ranges.length; i++) { + let { $from, $to } = ranges[i]; + has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontSize); + } + for (let i of ranges) { + if (has) { + toggleMark(view.state.schema.marks.pFontSize)(view.state, view.dispatch, view); + } + } + } + + let size = mark.attrs.fontSize; + if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } + if (this.editorProps) { + let ruleProvider = this.editorProps.ruleProvider; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleSize_" + heading] = size; + } + } + //actually apply font + if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { + let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, + { ...(view.state.selection as NodeSelection).node.attrs, setFontSize: size }), view.state.schema); + view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); + } + else view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, view.state.schema.marks.pFontSize.create({ fontSize: size }))); + view.state.storedMarks = [...(view.state.storedMarks || []), view.state.schema.marks.pFontSize.create({ fontSize: size })]; + } + //remove all node typeand apply the passed-in one to the selected text changeToNodeType = (nodeType: NodeType | undefined) => { //remove oldif (nodeType) { //add new @@ -558,7 +594,7 @@ export class TooltipTextMenu { //makes a button for the drop down FOR MARKS //css is the style you want applied to the button - dropdownMarkBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) { + dropdownFontFamilyBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontFamily: (mark: Mark, view: EditorView) => any) { return new MenuItem({ title: "", label: label, @@ -567,7 +603,22 @@ export class TooltipTextMenu { css: css, enable() { return true; }, run() { - changeToMarkInGroup(markType, view, groupMarks); + changeFontFamily(mark, view); + } + }); + } + //makes a button for the drop down FOR MARKS + //css is the style you want applied to the button + dropdownFontSizeBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontSize: (markType: Mark, view: EditorView) => any) { + return new MenuItem({ + title: "", + label: label, + execEvent: "", + class: "menuicon", + css: css, + enable() { return true; }, + run() { + changeFontSize(mark, view); } }); } @@ -649,7 +700,9 @@ export class TooltipTextMenu { this.view.dispatch(this.view.state.tr.removeMark(from, to)); Array.from(this._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { const markType = mark.type; - this.changeToMarkInGroup(markType, this.view, []); + if (mark.type === schema.marks.pFontFamily) this.changeToFontFamily(mark, this.view); + else if (mark.type === schema.marks.pFontSize) this.changeToFontSize(mark, this.view); + else this.changeToMarkInGroup(markType, this.view, []); }); } } @@ -868,50 +921,23 @@ export class TooltipTextMenu { //UPDATE LIST ITEM DROPDOWN //UPDATE FONT STYLE DROPDOWN - let activeStyles = this.activeMarksOnSelection(this.fontStyles); + let activeStyles = this.activeFontFamilyOnSelection(); if (activeStyles !== undefined) { - // activeStyles.forEach((markType) => { - // this._activeMarks.push(this.view.state.schema.mark(markType)); - // }); if (activeStyles.length === 1) { - // if we want to update something somewhere with active font name - let fontName = this.fontStylesToName.get(activeStyles[0]); - if (fontName) { this.updateFontStyleDropdown(fontName); } - } else if (activeStyles.length === 0) { - //crimson on default - this.updateFontStyleDropdown("Crimson Text"); - } else { - this.updateFontStyleDropdown("Various"); - } + activeStyles[0] && this.updateFontStyleDropdown(activeStyles[0]); + } else this.updateFontStyleDropdown(activeStyles.length ? "various" : "default"); } //UPDATE FONT SIZE DROPDOWN - let activeSizes = this.activeMarksOnSelection(this.fontSizes); + let activeSizes = this.activeFontSizeOnSelection(); if (activeSizes !== undefined) { if (activeSizes.length === 1) { //if there's only one active font size - // activeSizes.forEach((markType) => { - // this._activeMarks.push(this.view.state.schema.mark(markType)); - // }); - let size = this.fontSizeToNum.get(activeSizes[0]); - if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } - } else if (activeSizes.length === 0) { - //should be 14 on default - this.updateFontSizeDropdown("14 pt"); - } else { //multiple font sizes selected - this.updateFontSizeDropdown("Various"); - } + activeSizes[0] && this.updateFontSizeDropdown(String(activeSizes[0]) + " pt"); + } else this.updateFontSizeDropdown(activeSizes.length ? "various" : "default"); } this.update_mark_doms(); } - - public mark_key_pressed(marks: Mark[]) { - if (this.view.state.selection.empty) { - if (marks) this._activeMarks = marks; - this.update_mark_doms(); - } - } - update_mark_doms() { this.reset_mark_doms(); let foundlink = false; @@ -946,6 +972,30 @@ export class TooltipTextMenu { } + //finds fontSize at start of selection + activeFontSizeOnSelection() { + //current selection + let state = this.view.state; + let activeSizes: number[] = []; + const pos = this.view.state.selection.$from; + const ref_node: ProsNode = this.reference_node(pos); + if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { + ref_node.marks.forEach(m => m.type === state.schema.marks.pFontSize && activeSizes.push(m.attrs.fontSize)); + } + return activeSizes; + } + //finds fontSize at start of selection + activeFontFamilyOnSelection() { + //current selection + let state = this.view.state; + let activeFamilies: string[] = []; + const pos = this.view.state.selection.$from; + const ref_node: ProsNode = this.reference_node(pos); + if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { + ref_node.marks.forEach(m => m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family)); + } + return activeFamilies; + } //finds all active marks on selection in given group activeMarksOnSelection(markGroup: MarkType[]) { //current selection @@ -980,6 +1030,9 @@ export class TooltipTextMenu { this._activeMarks = ref_node.marks; activeMarks = markGroup.filter(mark_type => { if (dispatch) { + if (mark_type === state.schema.marks.pFontSize) { + return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); + } let mark = state.schema.mark(mark_type); return ref_node.marks.includes(mark); } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 037c92a62..abf26826c 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -93,8 +93,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F private _pushReactionDisposer: Opt; private dropDisposer?: DragManager.DragDropDisposer; - @observable private _fontSize = 13; - @observable private _fontFamily = "Arial"; + @observable private _ruleFontSize = 0; + @observable private _ruleFontFamily = "Arial"; @observable private _fontAlign = ""; @observable private _entered = false; public static SelectOnLoad = ""; @@ -187,9 +187,6 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F } const state = this._editorView.state.apply(tx); this._editorView.updateState(state); - if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) { - FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); - } let tsel = this._editorView.state.selection.$from; tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000))); @@ -552,8 +549,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F return undefined; }, action((rules: any) => { - this._fontFamily = rules ? rules.font : "Arial"; - this._fontSize = rules ? rules.size : NumCast(this.layoutDoc.fontSize, 13); + this._ruleFontFamily = rules ? rules.font : "Arial"; + this._ruleFontSize = rules ? rules.size : 0; rules && setTimeout(() => { const view = this._editorView!; if (this._proseRef) { @@ -1035,8 +1032,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1, color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit", pointerEvents: interactive, - fontSize: this._fontSize, - fontFamily: this._fontFamily, + fontSize: this._ruleFontSize ? this._ruleFontSize : NumCast(this.layoutDoc.fontSize, 13), + fontFamily: this._ruleFontFamily ? this._ruleFontFamily : StrCast(this.layoutDoc.fontFamily, "Crimson Text"), }} onContextMenu={this.specificContextMenu} onKeyDown={this.onKeyPress} -- cgit v1.2.3-70-g09d2 From d7150995d62c498ab8435de986b90d98bdca020c Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 12 Nov 2019 15:07:03 -0500 Subject: a bunch of cleanup and bug fixes to text links and doculink buttons --- src/client/documents/Documents.ts | 5 +- src/client/util/LinkManager.ts | 2 - src/client/util/RichTextSchema.tsx | 4 +- src/client/util/TooltipTextMenu.tsx | 23 +++--- src/client/views/DocumentDecorations.tsx | 10 ++- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/Main.tsx | 24 ------- src/client/views/MainView.scss | 2 +- src/client/views/MainView.tsx | 8 +-- src/client/views/linking/LinkMenuItem.tsx | 1 - src/client/views/nodes/FormattedTextBox.tsx | 9 ++- src/client/views/nodes/FormattedTextBoxComment.tsx | 83 ++++++++++++---------- src/client/views/nodes/PDFBox.scss | 2 + src/client/views/nodes/PDFBox.tsx | 76 +++++++++++--------- src/client/views/pdf/Annotation.tsx | 6 +- src/client/views/pdf/PDFViewer.tsx | 14 +--- src/new_fields/Doc.ts | 2 +- .../authentication/models/current_user_utils.ts | 1 - 18 files changed, 127 insertions(+), 147 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d1fcabc4a..ba9f87025 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -257,6 +257,9 @@ export namespace Docs { return PrototypeMap.get(type)!; } + /** + * A collection of all links in the database. Ideally, this would be a search, but for now all links are cached here. + */ export function MainLinkDocument() { return Prototypes.get(DocumentType.LINKDOC); } @@ -703,6 +706,7 @@ export namespace DocUtils { linkDocProto.title = title === "" ? source.doc.title + " to " + target.doc.title : title; linkDocProto.linkDescription = description; + linkDocProto.isPrototype = true; linkDocProto.anchor1 = source.doc; linkDocProto.anchor2 = target.doc; @@ -714,7 +718,6 @@ export namespace DocUtils { linkDocProto.anchor2Timecode = target.doc.currentTimecode; linkDocProto.layoutKey1 = DocuLinkBox.LayoutString("anchor1"); linkDocProto.layoutKey2 = DocuLinkBox.LayoutString("anchor2"); - linkDocProto.borderRounding = "20"; linkDocProto.width = linkDocProto.height = 0; linkDocProto.isBackground = true; linkDocProto.isButton = true; diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index ee2f2dadc..eedc4967d 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -34,8 +34,6 @@ export class LinkManager { // the linkmanagerdoc stores a list of docs representing all linkdocs in 'allLinks' and a list of strings representing all group types in 'allGroupTypes' // lists of strings representing the metadata keys for each group type is stored under a key that is the same as the group type public get LinkManagerDoc(): Doc | undefined { - // return FieldValue(Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc)); - return Docs.Prototypes.MainLinkDocument(); } diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index a5e0ca720..76b8aeaa1 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -305,8 +305,8 @@ export const marks: { [index: string]: MarkSpec } = { }], toDOM(node: any) { return node.attrs.docref && node.attrs.title ? - ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : - ["a", { ...node.attrs }, 0]; + ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] : + ["a", { ...node.attrs, title: `${node.attrs.title}` }, 0]; } }, diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 9ce7acec8..38471a955 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -17,7 +17,7 @@ import { DragManager } from "./DragManager"; import { LinkManager } from "./LinkManager"; import { schema } from "./RichTextSchema"; import "./TooltipTextMenu.scss"; -import { Cast, NumCast } from '../../new_fields/Types'; +import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { updateBullets } from './ProsemirrorExampleTransfer'; import { DocumentDecorations } from '../views/DocumentDecorations'; const { toggleMark, setBlockType } = require("prosemirror-commands"); @@ -284,7 +284,7 @@ export class TooltipTextMenu { if (proto && docView) { proto.sourceContext = docView.props.ContainingCollectionDoc; } - let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab"); + let text = this.makeLink(linkDoc, StrCast(linkDoc.anchor2.title), ctrlKey ? "onRight" : "inTab"); if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) { proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link } @@ -374,25 +374,20 @@ export class TooltipTextMenu { // let link = state.schema.mark(state.schema.marks.link, { href: target, location: location }); // } - makeLink = (targetDoc: Doc, location: string): string => { - let target = Utils.prepend("/doc/" + targetDoc[Id]); + makeLink = (targetDoc: Doc, title: string, location: string): string => { + let link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + targetDoc[Id]), title: title, location: location }); + this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link). + addMark(this.view.state.selection.from, this.view.state.selection.to, link)); let node = this.view.state.selection.$from.nodeAfter; - let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location, guid: targetDoc[Id] }); - this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link)); - this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link)); - node = this.view.state.selection.$from.nodeAfter; - link = node && node.marks.find(m => m.type.name === "link"); - if (node) { - if (node.text) { - return node.text; - } + if (node && node.text) { + return node.text; } return ""; } deleteLink = () => { let node = this.view.state.selection.$from.nodeAfter; - let link = node && node.marks.find(m => m.type.name === "link"); + let link = node && node.marks.find(m => m.type === this.view.state.schema.marks.link); let href = link!.attrs.href; if (href) { if (href.indexOf(Utils.prepend("/doc/")) === 0) { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 0336440d5..55c211d1d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -24,6 +24,7 @@ import { DocumentView } from "./nodes/DocumentView"; import { FieldView } from "./nodes/FieldView"; import { IconBox } from "./nodes/IconBox"; import React = require("react"); +import { DocumentType } from '../documents/DocumentTypes'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -112,7 +113,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } else { if (SelectionManager.SelectedDocuments().length > 0) { - SelectionManager.SelectedDocuments()[0].props.Document.customTitle = true; + SelectionManager.SelectedDocuments()[0].props.Document.customTitle = !this._title.startsWith("-"); let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey]; if (typeof field === "number") { SelectionManager.SelectedDocuments().forEach(d => { @@ -174,6 +175,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> var [sptX, sptY] = transform.transformPoint(0, 0); let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight()); + if (documentView.props.Document.type === DocumentType.LINK) { + let rect = documentView.ContentDiv!.getElementsByClassName("docuLinkBox-cont")[0].getBoundingClientRect(); + sptX = rect.left; + sptY = rect.top; + bptX = rect.right; + bptY = rect.bottom; + } return { x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 9ca9fc163..8f397e331 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -165,7 +165,7 @@ export default class KeyManager { } } break; - case "c": + case "t": PromiseValue(Cast(CurrentUserUtils.UserDocument.Create, Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv })); if (MainView.Instance.flyoutWidth === 240) { MainView.Instance.flyoutWidth = 0; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index a91a2b69e..b21eb9c8f 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -3,34 +3,11 @@ import { Docs } from "../documents/Documents"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; import * as ReactDOM from 'react-dom'; import * as React from 'react'; -import { Cast } from "../../new_fields/Types"; -import { Doc, DocListCastAsync } from "../../new_fields/Doc"; -import { List } from "../../new_fields/List"; import { DocServer } from "../DocServer"; import { AssignAllExtensions } from "../../extensions/General/Extensions"; AssignAllExtensions(); -let swapDocs = async () => { - let oldDoc = await Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc); - // Docs.Prototypes.MainLinkDocument().allLinks = new List(); - if (oldDoc) { - let links = await DocListCastAsync(oldDoc.allLinks); - // if (links && DocListCast(links)) { - if (links && links.length) { - let data = await DocListCastAsync(Docs.Prototypes.MainLinkDocument().allLinks); - if (data) { - data.push(...links.filter(i => data!.indexOf(i) === -1)); - Docs.Prototypes.MainLinkDocument().allLinks = new List(data.filter((i, idx) => data!.indexOf(i) === idx)); - } - else { - Docs.Prototypes.MainLinkDocument().allLinks = new List(links); - } - } - CurrentUserUtils.UserDocument.linkManagerDoc = undefined; - } -}; - (async () => { const info = await CurrentUserUtils.loadCurrentUser(); DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email); @@ -38,7 +15,6 @@ let swapDocs = async () => { if (info.id !== "__guest__") { // a guest will not have an id registered await CurrentUserUtils.loadUserDocument(info); - await swapDocs(); } document.getElementById('root')!.addEventListener('wheel', event => { if (event.ctrlKey) { diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index 21b135c49..a858a73c7 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -13,7 +13,7 @@ left: 250px; } -.mainView-container { +#mainView-container { width: 100%; height: 100%; position: absolute; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 773da05df..83dbb433b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -37,7 +37,6 @@ import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import { Scripting } from '../util/Scripting'; -import { LinkManager } from '../util/LinkManager'; import { AudioBox } from './nodes/AudioBox'; @observer @@ -197,11 +196,6 @@ export class MainView extends React.Component { var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] }; let mainDoc = Docs.Create.DockDocument([freeformDoc], JSON.stringify(dockingLayout), {}, id); if (this.userDoc && ((workspaces = Cast(this.userDoc.workspaces, Doc)) instanceof Doc)) { - if (!this.userDoc.linkManagerDoc) { - let linkManagerDoc = new Doc(); - linkManagerDoc.allLinks = new List([]); - this.userDoc.linkManagerDoc = linkManagerDoc; - } Doc.AddDocToList(workspaces, "data", mainDoc); mainDoc.title = `Workspace ${DocListCast(workspaces.data).length}`; } @@ -504,7 +498,7 @@ export class MainView extends React.Component { } render() { - return (
+ return (
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index a6ee9c2c6..238660de3 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -8,7 +8,6 @@ import { Cast, StrCast } from '../../../new_fields/Types'; import { DragLinkAsDocument } from '../../util/DragManager'; import { LinkManager } from '../../util/LinkManager'; import { ContextMenu } from '../ContextMenu'; -import { MainView } from '../MainView'; import { LinkFollowBox } from './LinkFollowBox'; import './LinkMenu.scss'; import React = require("react"); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index abf26826c..a0f8523a2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -842,6 +842,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F this._editorView && this._editorView.destroy(); } onPointerDown = (e: React.PointerEvent): void => { + FormattedTextBoxComment.textBox = this; let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos)); if (this.props.onClick && e.button === 0) { @@ -1018,11 +1019,13 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F } render() { + trace(); let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; - let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground - ? "none" : "all"; + let interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground; if (this.props.isSelected()) { FormattedTextBox._toolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props); + } else if (FormattedTextBoxComment.textBox === this) { + FormattedTextBoxComment.Hide(); } return (
{ let keep = e.target && (e.target as any).type === "checkbox" ? true : false; @@ -91,62 +93,67 @@ export class FormattedTextBoxComment { let state = view.state; // Don't do anything if the document/selection didn't change if (lastState && lastState.doc.eq(state.doc) && - lastState.selection.eq(state.selection)) return; + lastState.selection.eq(state.selection)) { + return; + } - if (!FormattedTextBoxComment.textBox || !FormattedTextBoxComment.textBox.props || !FormattedTextBoxComment.textBox.props.isSelected()) return; + const textBox = FormattedTextBoxComment.textBox; + if (!textBox || !textBox.props) { + return; + } let set = "none"; - if (FormattedTextBoxComment.textBox && state.selection.$from) { - let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); + let nbef = 0; + // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date + if (state.selection.$from) { + nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); - const spos = state.selection.$from.pos - nbef; - const epos = state.selection.$from.pos + naft; - let child = state.selection.$from.nodeBefore; - let mark = child && findOtherUserMark(child.marks); let noselection = view.state.selection.$from === view.state.selection.$to; + let child: any = null; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); + let mark = child && findOtherUserMark(child.marks); if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { - FormattedTextBoxComment.SetState(this, mark.attrs.opened, spos, epos, mark); + FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, mark.attrs.opened, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); } - if (mark && child && nbef && naft) { - FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " " + mark.attrs.modified; - // These are in screen coordinates - // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); - let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); - // The box in which the tooltip is positioned, to use as base - let box = (document.getElementById("main-div") as any).getBoundingClientRect(); - // Find a center-ish x position from the selection endpoints (when - // crossing lines, end may be more to the left) - let left = Math.max((start.left + end.left) / 2, start.left + 3); - FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; - FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; + if (mark && child && ((nbef && naft) || !noselection)) { + FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString(); set = ""; } } + // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. if (set === "none" && state.selection.$from) { - FormattedTextBoxComment.textBox = undefined; - let nbef = findStartOfMark(state.selection.$from, view, findLinkMark); + nbef = findStartOfMark(state.selection.$from, view, findLinkMark); let naft = findEndOfMark(state.selection.$from, view, findLinkMark); - let child = state.selection.$from.nodeBefore; + let child: any = null; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); let mark = child && findLinkMark(child.marks); if (mark && child && nbef && naft) { - FormattedTextBoxComment.tooltipText.textContent = "link : " + (mark.attrs.title || mark.attrs.href); + FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href; if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - docTarget && DocServer.GetRefField(docTarget).then(linkDoc => - (linkDoc as Doc) && (FormattedTextBoxComment.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc).title))); + docTarget && DocServer.GetRefField(docTarget).then(linkDoc => { + if (linkDoc instanceof Doc) { + let target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); + let ext = (target && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... + let text = ext && StrCast(ext.text); + ext && (FormattedTextBoxComment.tooltipText.textContent = "=> " + (text || StrCast(ext.title))); + } + }); } - // These are in screen coordinates - // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); - let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); - // The box in which the tooltip is positioned, to use as base - let box = (document.getElementById("main-div") as any).getBoundingClientRect(); - // Find a center-ish x position from the selection endpoints (when - // crossing lines, end may be more to the left) - let left = Math.max((start.left + end.left) / 2, start.left + 3); - FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; - FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; set = ""; } } + if (set !== "none") { + // These are in screen coordinates + // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); + let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); + // The box in which the tooltip is positioned, to use as base + let box = (document.getElementById("mainView-container") as any).getBoundingClientRect(); + // Find a center-ish x position from the selection endpoints (when + // crossing lines, end may be more to the left) + let left = Math.max((start.left + end.left) / 2, start.left + 3); + FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; + FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; + } FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set); } diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index 963205206..2d92c9581 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -48,6 +48,7 @@ } .pdfViewer-text { .textLayer { + will-change: transform; span { user-select: none; } @@ -59,6 +60,7 @@ pointer-events: all; .pdfViewer-text { .textLayer { + will-change: transform; span { user-select: text; } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 3baa6eb09..8e0515f8a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, observable, runInAction, reaction, IReactionDisposer, trace } from 'mobx'; +import { action, observable, runInAction, reaction, IReactionDisposer, trace, untracked, computed } from 'mobx'; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; @@ -32,7 +32,7 @@ export class PDFBox extends DocAnnotatableComponent private _valueValue: string = ""; private _scriptValue: string = ""; private _searchString: string = ""; - private _initialScale: number | undefined; // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not. Getting bigger after startup won't make it automatically be live. + private _initialScale: number = 0; // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not. Getting bigger after startup won't make it automatically be live. private _everActive = false; // has this box ever had its contents activated -- if so, stop drawing the overlay title private _pdfViewer: PDFViewer | undefined; private _searchRef = React.createRef(); @@ -46,6 +46,11 @@ export class PDFBox extends DocAnnotatableComponent @observable private _pdf: Opt; @observable private _pageControls = false; + constructor(props: any) { + super(props); + this._initialScale = this.props.ScreenToLocalTransform().Scale; + } + componentWillUnmount() { this._selectReactionDisposer && this._selectReactionDisposer(); } @@ -190,39 +195,46 @@ export class PDFBox extends DocAnnotatableComponent ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" }); } - render() { + @computed get renderTitleBox() { + let classname = "pdfBox-cont" + (this.active() ? "-interactive" : ""); + return
+
+ {` ${this.props.Document.title}`} +
+
; + } + + @computed get renderPdfView() { trace(); const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); let classname = "pdfBox-cont" + (this.active() ? "-interactive" : ""); - let noPdf = !(pdfUrl instanceof PdfField) || !this._pdf; - if (this._initialScale === undefined) this._initialScale = this.props.ScreenToLocalTransform().Scale; + return
{ + let hit = document.elementFromPoint(e.clientX, e.clientY); + if (hit && hit.localName === "span" && this.props.isSelected()) { // drag selecting text stops propagation + e.button === 0 && e.stopPropagation(); + } + }}> + + {this.settingsPanel()} +
; + } + + render() { + const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null); if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true; - return (!this.extensionDoc || noPdf || (!this._everActive && this.props.ScreenToLocalTransform().Scale > 2.5) ? -
-
- {` ${this.props.Document.title}`} -
-
: -
{ - let hit = document.elementFromPoint(e.clientX, e.clientY); - if (hit && hit.localName === "span" && this.props.isSelected()) { // drag selecting text stops propagation - e.button === 0 && e.stopPropagation(); - } - }}> - - {this.settingsPanel()} -
); + return !pdfUrl || !this._pdf || !this.extensionDoc || (!this._everActive && this.props.ScreenToLocalTransform().Scale > 2.5) ? + this.renderTitleBox : this.renderPdfView; } } \ No newline at end of file diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 2d8f47666..936af9ab8 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -52,11 +52,7 @@ class RegionAnnotation extends React.Component { this._brushDisposer = reaction( () => FieldValue(Cast(this.props.document.group, Doc)) && Doc.isBrushedHighlightedDegree(FieldValue(Cast(this.props.document.group, Doc))!), - (brushed) => { - if (brushed !== undefined) { - runInAction(() => this._brushed = brushed !== 0); - } - } + brushed => brushed !== undefined && runInAction(() => this._brushed = brushed !== 0) ); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 060ba8613..0cb671156 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -199,7 +199,7 @@ export class PDFViewer extends DocAnnotatableComponent this.extensionDoc && DocListCast(this.extensionDoc.annotations), - annotations => annotations && annotations.length && this.renderAnnotations(annotations, true), + annotations => annotations && annotations.length && (this._annotations = annotations), { fireImmediately: true }); this._filterReactionDisposer = reaction( @@ -297,18 +297,6 @@ export class PDFViewer extends DocAnnotatableComponent { - if (removeOldAnnotations) { - this._annotations = annotations; - } - else { - this._annotations.push(...annotations); - this._annotations = new Array(...this._annotations); - } - } - @action prevAnnotation = () => { this.Index = Math.max(this.Index - 1, 0); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 6aad4a6be..3bf1129b5 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -669,8 +669,8 @@ export namespace Doc { return doc; } - export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; } + export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; } export function linkFollowUnhighlight() { Doc.UnhighlightAll(); document.removeEventListener("pointerdown", linkFollowUnhighlight); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 56ea5bfe1..833e44bf6 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -15,7 +15,6 @@ import { RouteStore } from "../../RouteStore"; import { InkingControl } from "../../../client/views/InkingControl"; import { DragManager } from "../../../client/util/DragManager"; import { nullAudio } from "../../../new_fields/URLField"; -import { LinkManager } from "../../../client/util/LinkManager"; export class CurrentUserUtils { private static curr_id: string; -- cgit v1.2.3-70-g09d2 From 76411ba009c4265751431d255ab3cc9a3cfab69f Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 12 Nov 2019 21:28:39 -0500 Subject: switched link preview to be a document view --- src/client/util/RichTextSchema.tsx | 1 - .../views/nodes/ContentFittingDocumentView.tsx | 2 + src/client/views/nodes/DocumentView.tsx | 6 ++- src/client/views/nodes/FormattedTextBoxComment.tsx | 62 +++++++++++++++++----- 4 files changed, 54 insertions(+), 17 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 76b8aeaa1..1004cb3d4 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -18,7 +18,6 @@ import { Transform } from "./Transform"; import React = require("react"); import { BoolCast, NumCast } from "../../new_fields/Types"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index f41c4fc91..751355403 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -37,6 +37,7 @@ interface ContentFittingDocumentViewProps { whenActiveChanged: (isActive: boolean) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; + dontRegisterView?: boolean; setPreviewScript: (script: string) => void; previewScript?: string; } @@ -108,6 +109,7 @@ export class ContentFittingDocumentView extends React.Component diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 93052ea73..4fbb0516f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -74,6 +74,7 @@ export interface DocumentViewProps { getScale: () => number; animateBetweenIcon?: (maximize: boolean, target: number[]) => void; ChromeHeight?: () => number; + dontRegisterView?: boolean; layoutKey?: string; } @@ -98,7 +99,8 @@ export class DocumentView extends DocComponent(Docu @action componentDidMount() { this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } })); - DocumentManager.Instance.DocumentViews.push(this); + + !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this); } @action @@ -111,7 +113,7 @@ export class DocumentView extends DocComponent(Docu componentWillUnmount() { this._dropDisposer && this._dropDisposer(); Doc.UnBrushDoc(this.props.Document); - DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); + !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); } startDragging(x: number, y: number, dropAction: dropActionType, applyAsTemplate?: boolean) { diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index f35ca502f..98cd17972 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -2,16 +2,19 @@ import { Plugin, EditorState } from "prosemirror-state"; import './FormattedTextBoxComment.scss'; import { ResolvedPos, Mark } from "prosemirror-model"; import { EditorView } from "prosemirror-view"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, WidthSym } from "../../../new_fields/Doc"; import { schema } from "../../util/RichTextSchema"; import { DocServer } from "../../DocServer"; -import { Utils } from "../../../Utils"; -import { StrCast, Cast, FieldValue } from "../../../new_fields/Types"; +import { Utils, returnTrue, returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../../Utils"; +import { StrCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types"; import { FormattedTextBox } from "./FormattedTextBox"; -import { DocUtils } from "../../documents/Documents"; -import { isBuffer } from "util"; import { DocumentManager } from "../../util/DocumentManager"; import { DocumentType } from "../../documents/DocumentTypes"; +import { DocumentView } from "./DocumentView"; +import React = require("react"); +import * as ReactDOM from 'react-dom'; +import { Transform } from "../../util/Transform"; +import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; export let formattedTextBoxCommentPlugin = new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -67,22 +70,28 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip"; FormattedTextBoxComment.tooltip.style.pointerEvents = "all"; FormattedTextBoxComment.tooltip.style.maxWidth = "350px"; + FormattedTextBoxComment.tooltip.style.maxHeight = "250px"; + FormattedTextBoxComment.tooltip.style.width = "100%"; + FormattedTextBoxComment.tooltip.style.height = "100%"; + FormattedTextBoxComment.tooltip.style.overflow = "hidden"; + FormattedTextBoxComment.tooltip.style.display = "none"; FormattedTextBoxComment.tooltip.appendChild(input); FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => { let keep = e.target && (e.target as any).type === "checkbox" ? true : false; - if (FormattedTextBoxComment.linkDoc && !keep && FormattedTextBoxComment.textBox) { - DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, FormattedTextBoxComment.textBox.props.Document, - (doc: Doc, maxLocation: string) => FormattedTextBoxComment.textBox!.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight")); + const textBox = FormattedTextBoxComment.textBox; + if (FormattedTextBoxComment.linkDoc && !keep && textBox) { + DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, + (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight")); } FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened; - FormattedTextBoxComment.textBox && FormattedTextBoxComment.start !== undefined && FormattedTextBoxComment.textBox.setAnnotation( + textBox && FormattedTextBoxComment.start !== undefined && textBox.setAnnotation( FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark, FormattedTextBoxComment.opened, keep); e.stopPropagation(); }; root && root.appendChild(FormattedTextBoxComment.tooltip); } - this.update(view, undefined); + //this.update(view, undefined); } public static Hide() { @@ -144,9 +153,34 @@ export class FormattedTextBoxComment { if (linkDoc instanceof Doc) { FormattedTextBoxComment.linkDoc = linkDoc; let target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); - let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... - let text = ext && StrCast(ext.text); - ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title))); + try { + ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); + } catch (e) { + + } + target && ReactDOM.render( 350} + PanelHeight={() => 250} + focus={emptyFunction} + whenActiveChanged={returnFalse} + />, FormattedTextBoxComment.tooltipText); + // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... + // let text = ext && StrCast(ext.text); + // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title))); } }); } @@ -168,5 +202,5 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set); } - destroy() { FormattedTextBoxComment.tooltip.style.display = "none"; } + destroy() { }//FormattedTextBoxComment.tooltip.style.display = "none"; } } -- cgit v1.2.3-70-g09d2 From a93ee99c9dd3b5ecc5160f98f9dffeb119b4dcce Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 13 Nov 2019 13:46:23 -0500 Subject: fixed recentlyClosed on startup and added clearAll. fixed textbox comment layout a bit. --- src/client/util/SearchUtil.ts | 7 ++- .../views/collections/CollectionStackingView.tsx | 1 - .../views/collections/CollectionTreeView.tsx | 13 ++-- .../views/nodes/ContentFittingDocumentView.tsx | 1 - src/client/views/nodes/FormattedTextBox.tsx | 5 +- .../views/nodes/FormattedTextBoxComment.scss | 1 - src/client/views/nodes/FormattedTextBoxComment.tsx | 71 +++++++++++----------- .../views/presentationview/PresElementBox.tsx | 1 - .../authentication/models/current_user_utils.ts | 2 + 9 files changed, 56 insertions(+), 46 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 6706dcb89..2cf13680a 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -28,6 +28,7 @@ export namespace SearchUtil { start?: number; rows?: number; fq?: string; + allowAliases?: boolean; } export function Search(query: string, returnDocs: true, options?: SearchParams): Promise; export function Search(query: string, returnDocs: false, options?: SearchParams): Promise; @@ -73,7 +74,7 @@ export namespace SearchUtil { const docs = ids.map((id: string) => docMap[id]).map(doc => doc as Doc); for (let i = 0; i < ids.length; i++) { let testDoc = docs[i]; - if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) { + if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && (options.allowAliases || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) { theDocs.push(testDoc); theLines.push([]); } @@ -88,9 +89,9 @@ export namespace SearchUtil { const proto = Doc.GetProto(doc); const protoId = proto[Id]; if (returnDocs) { - return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"` })).docs; + return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"`, allowAliases: true })).docs; } else { - return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"` })).ids; + return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"`, allowAliases: true })).ids; } // return Search(`{!join from=id to=proto_i}id:${protoId}`, true); } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 15033e51a..be3bfca0a 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -168,7 +168,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return { />) onWorkspaceContextMenu = (e: React.MouseEvent): void => { - if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 - if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking && this.props.document !== CurrentUserUtils.UserDocument.workspaces) { + if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view + if (this.props.document === CurrentUserUtils.UserDocument.recentlyClosed) { + ContextMenu.Instance.addItem({ description: "Clear All", event: () => Doc.GetProto(CurrentUserUtils.UserDocument.recentlyClosed as Doc).data = new List(), icon: "plus" }); + } else if (this.props.document !== CurrentUserUtils.UserDocument.workspaces) { ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.document), icon: "tv" }); ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.templateDataDoc, "inTab"), icon: "folder" }); ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.templateDataDoc, "onRight"), icon: "caret-square-right" }); @@ -317,7 +318,6 @@ class TreeView extends React.Component { CurrentUserUtils.UserDocument.recentlyClosed = new List(), icon: "plus" }); + e.stopPropagation(); + e.preventDefault(); + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } else { let layoutItems: ContextMenuProps[] = []; layoutItems.push({ description: this.props.Document.preventTreeViewOpen ? "Persist Treeview State" : "Abandon Treeview State", event: () => this.props.Document.preventTreeViewOpen = !this.props.Document.preventTreeViewOpen, icon: "paint-brush" }); diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index 751355403..c8255b6fe 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -20,7 +20,6 @@ interface ContentFittingDocumentViewProps { childDocs?: Doc[]; renderDepth: number; fitToBox?: boolean; - fieldKey: string; PanelWidth: () => number; PanelHeight: () => number; ruleProvider: Doc | undefined; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 65a51b357..8b1e65663 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -843,7 +843,10 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F } onPointerUp = (e: React.PointerEvent): void => { - if (!(e.nativeEvent as any).formattedHandled) { FormattedTextBoxComment.textBox = this; } + if (!(e.nativeEvent as any).formattedHandled) { + FormattedTextBoxComment.textBox = this; + FormattedTextBoxComment.update(this._editorView!); + } (e.nativeEvent as any).formattedHandled = true; if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { diff --git a/src/client/views/nodes/FormattedTextBoxComment.scss b/src/client/views/nodes/FormattedTextBoxComment.scss index 792cee182..2dd63ec21 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.scss +++ b/src/client/views/nodes/FormattedTextBoxComment.scss @@ -5,7 +5,6 @@ background: white; border: 1px solid silver; border-radius: 2px; - padding: 2px 10px; margin-bottom: 7px; -webkit-transform: translateX(-50%); transform: translateX(-50%); diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 98cd17972..29b4b6383 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -1,20 +1,18 @@ -import { Plugin, EditorState } from "prosemirror-state"; -import './FormattedTextBoxComment.scss'; -import { ResolvedPos, Mark } from "prosemirror-model"; +import { Mark, ResolvedPos } from "prosemirror-model"; +import { EditorState, Plugin } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import { Doc, WidthSym } from "../../../new_fields/Doc"; -import { schema } from "../../util/RichTextSchema"; +import * as ReactDOM from 'react-dom'; +import { Doc } from "../../../new_fields/Doc"; +import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; +import { emptyFunction, returnEmptyString, returnFalse, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; -import { Utils, returnTrue, returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../../Utils"; -import { StrCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types"; -import { FormattedTextBox } from "./FormattedTextBox"; import { DocumentManager } from "../../util/DocumentManager"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { DocumentView } from "./DocumentView"; -import React = require("react"); -import * as ReactDOM from 'react-dom'; +import { schema } from "../../util/RichTextSchema"; import { Transform } from "../../util/Transform"; import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; +import { FormattedTextBox } from "./FormattedTextBox"; +import './FormattedTextBoxComment.scss'; +import React = require("react"); export let formattedTextBoxCommentPlugin = new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -107,7 +105,7 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = ""); } - update(view: EditorView, lastState?: EditorState) { + static update(view: EditorView, lastState?: EditorState) { let state = view.state; // Don't do anything if the document/selection didn't change if (lastState && lastState.doc.eq(state.doc) && @@ -122,6 +120,8 @@ export class FormattedTextBoxComment { } let set = "none"; let nbef = 0; + FormattedTextBoxComment.tooltip.style.width = ""; + FormattedTextBoxComment.tooltip.style.height = ""; // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date if (state.selection.$from) { nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); @@ -152,32 +152,35 @@ export class FormattedTextBoxComment { docTarget && DocServer.GetRefField(docTarget).then(linkDoc => { if (linkDoc instanceof Doc) { FormattedTextBoxComment.linkDoc = linkDoc; - let target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); + const target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); try { ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); } catch (e) { } - target && ReactDOM.render( 350} - PanelHeight={() => 250} - focus={emptyFunction} - whenActiveChanged={returnFalse} - />, FormattedTextBoxComment.tooltipText); + if (target) { + ReactDOM.render( Math.min(350, NumCast(target.width, 350))} + PanelHeight={() => Math.min(250, NumCast(target.height, 250))} + focus={emptyFunction} + whenActiveChanged={returnFalse} + />, FormattedTextBoxComment.tooltipText); + FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%"; + FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%"; + } // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... // let text = ext && StrCast(ext.text); // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title))); diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 17b2094ec..f50a3a0ef 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -172,7 +172,6 @@ export class PresElementBox extends DocComponent(P recent && PromiseValue(recent.data).then(DocListCast)); // this is equivalent to using PrefetchProxies to make sure all the sidebarButtons and noteType internal Doc's have been retrieved. PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(DocListCast)); PromiseValue(Cast(doc.sidebarButtons, Doc)).then(stackingDoc => { -- cgit v1.2.3-70-g09d2 From e87b4b99323875afce2d9847f3bddd4196b85b81 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 13 Nov 2019 14:48:57 -0500 Subject: added a lockedTransform field to lock pan/zoom. fixed text scrollintoview to scroll only when necessary. --- src/client/documents/Documents.ts | 3 ++- src/client/util/RichTextSchema.tsx | 1 + .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- src/client/views/nodes/DocumentView.tsx | 7 +++++++ src/client/views/nodes/FormattedTextBox.tsx | 4 +++- src/new_fields/documentSchemas.ts | 3 ++- 6 files changed, 17 insertions(+), 5 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ba9f87025..1a9d67d83 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -76,7 +76,8 @@ export interface DocumentOptions { viewType?: number; backgroundColor?: string; ignoreClick?: boolean; - lockedPosition?: boolean; + lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged + lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed opacity?: number; defaultBackgroundColor?: string; dropAction?: dropActionType; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 1004cb3d4..0d1ae3841 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -679,6 +679,7 @@ export class DashDocView { bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne} + dontRegisterView={true} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} ContentScaling={this.contentScaling} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 6e0f75bc1..0c5f4ec80 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -333,7 +333,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerWheel = (e: React.WheelEvent): void => { - if (this.props.Document.lockedPosition || this.props.Document.inOverlay) return; + if (this.props.Document.lockedTransform || this.props.Document.inOverlay) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } @@ -355,7 +355,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action setPan(panX: number, panY: number, panType: string = "None") { - if (!this.Document.lockedPosition || this.Document.inOverlay) { + if (!this.Document.lockedTransform || this.Document.inOverlay) { this.Document.panTransformType = panType; var scale = this.getLocalTransform().inverse().Scale; const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7e81cd673..98c610c68 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -372,6 +372,12 @@ export class DocumentView extends DocComponent(Docu this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true; } + @undoBatch + @action + toggleLockTransform = (): void => { + this.Document.lockedTransform = this.Document.lockedTransform ? undefined : true; + } + listen = async () => { Doc.GetProto(this.props.Document).transcript = await DictationManager.Controls.listen({ continuous: { indefinite: true }, @@ -444,6 +450,7 @@ export class DocumentView extends DocComponent(Docu layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" }); layoutItems.push({ description: this.Document.ignoreAspect || !this.Document.nativeWidth || !this.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); + layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" }); layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" }); layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" }); if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8b1e65663..015a21fd2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -774,7 +774,9 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement; let r1 = refNode && refNode.getBoundingClientRect(); let r3 = self._ref.current!.getBoundingClientRect(); - r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale); + if (r1.top < r3.top || r1.top > r3.bottom) { + r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale); + } return true; }, dispatchTransaction: this.dispatchTransaction, diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index e2730914f..fa47374f1 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -31,7 +31,8 @@ export const documentSchema = createSchema({ summarizedDocs: listSpec(Doc), // documents that are summarized by this document (and which will typically be opened by clicking this document) maximizedDocs: listSpec(Doc), // documents to maximize when clicking this document (generally this document will be an icon) maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) - lockedPosition: "boolean", // whether the document can be spatially manipulated + lockedPosition: "boolean", // whether the document can be moved (dragged) + lockedTransform: "boolean", // whether the document can be panned/zoomed inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently borderRounding: "string", // border radius rounding of document searchFields: "string", // the search fields to display when this document matches a search in its metadata -- cgit v1.2.3-70-g09d2 From c6b602e2ab00d3a38e56164ecc928d5db8c12c72 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 14 Nov 2019 11:35:33 -0500 Subject: fixed warning/errors. cleaned up link following box a bit. --- src/client/util/RichTextSchema.tsx | 4 ++-- src/client/util/TooltipTextMenu.tsx | 20 ++++++++----------- .../views/collections/CollectionSchemaView.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FontIconBox.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 1 + src/client/views/nodes/FormattedTextBoxComment.tsx | 23 ++++++++++++++++------ src/client/views/search/FilterBox.tsx | 4 ++-- 8 files changed, 32 insertions(+), 25 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 0d1ae3841..0fc526ca7 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -575,8 +575,8 @@ export class ImageResizeView { this._handle.onpointerdown = function (e: any) { e.preventDefault(); e.stopPropagation(); - let wid = Number(getComputedStyle(self._img).width!.replace(/px/, "")); - let hgt = Number(getComputedStyle(self._img).height!.replace(/px/, "")); + let wid = Number(getComputedStyle(self._img).width.replace(/px/, "")); + let hgt = Number(getComputedStyle(self._img).height.replace(/px/, "")); const startX = e.pageX; const startWidth = parseFloat(node.attrs.width); const onpointermove = (e: any) => { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 38471a955..5e11ae653 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1000,12 +1000,10 @@ export class TooltipTextMenu { let activeMarks: MarkType[]; if (!empty) { activeMarks = markGroup.filter(mark => { - if (dispatch) { - let has = false; - for (let i = 0; !has && i < ranges.length; i++) { - let { $from, $to } = ranges[i]; - return state.doc.rangeHasMark($from.pos, $to.pos, mark); - } + let has = false; + for (let i = 0; !has && i < ranges.length; i++) { + let { $from, $to } = ranges[i]; + return state.doc.rangeHasMark($from.pos, $to.pos, mark); } return false; }); @@ -1024,13 +1022,11 @@ export class TooltipTextMenu { } this._activeMarks = ref_node.marks; activeMarks = markGroup.filter(mark_type => { - if (dispatch) { - if (mark_type === state.schema.marks.pFontSize) { - return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); - } - let mark = state.schema.mark(mark_type); - return ref_node.marks.includes(mark); + if (mark_type === state.schema.marks.pFontSize) { + return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); } + let mark = state.schema.mark(mark_type); + return ref_node.marks.includes(mark); return false; }); } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 203c68463..ebd47fd19 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -141,7 +141,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { (Docu {searchHighlight}
} - + ; } render() { if (!this.props.Document) return (null); diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index ae9273639..83ecc4657 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -25,7 +25,7 @@ export class FontIconBox extends DocComponent( this._backgroundReaction = reaction(() => this.props.Document.backgroundColor, () => { if (this._ref && this._ref.current) { - let col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor!); + let col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor); let colsum = (col.r + col.g + col.b); if (colsum / col.a > 600 || col.a < 0.25) runInAction(() => this._foregroundColor = "black"); else if (colsum / col.a <= 600 || col.a >= .25) runInAction(() => this._foregroundColor = "white"); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 015a21fd2..6d60e8f77 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -288,6 +288,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F if (context === node) return { from: offset, to: offset + node.nodeSize }; if (node.isBlock) { + // tslint:disable-next-line: prefer-for-of for (let i = 0; i < (context.content as any).content.length; i++) { let result = this.getNodeEndpoints((context.content as any).content[i], node); if (result) { diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 29b4b6383..32efb16cf 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -13,6 +13,7 @@ import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; import { FormattedTextBox } from "./FormattedTextBox"; import './FormattedTextBoxComment.scss'; import React = require("react"); +import { Docs } from "../../documents/Documents"; export let formattedTextBoxCommentPlugin = new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -51,6 +52,7 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark export class FormattedTextBoxComment { static tooltip: HTMLElement; static tooltipText: HTMLElement; + static tooltipInput: HTMLInputElement; static start: number; static end: number; static mark: Mark; @@ -60,10 +62,15 @@ export class FormattedTextBoxComment { constructor(view: any) { if (!FormattedTextBoxComment.tooltip) { const root = document.getElementById("root"); - let input = document.createElement("input"); - input.type = "checkbox"; + FormattedTextBoxComment.tooltipInput = document.createElement("input"); + FormattedTextBoxComment.tooltipInput.type = "checkbox"; FormattedTextBoxComment.tooltip = document.createElement("div"); FormattedTextBoxComment.tooltipText = document.createElement("div"); + FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre"; + FormattedTextBoxComment.tooltipText.style.overflow = "hidden"; + FormattedTextBoxComment.tooltipText.style.width = "100%"; + FormattedTextBoxComment.tooltipText.style.height = "100%"; + FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis"; FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText); FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip"; FormattedTextBoxComment.tooltip.style.pointerEvents = "all"; @@ -73,13 +80,15 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip.style.height = "100%"; FormattedTextBoxComment.tooltip.style.overflow = "hidden"; FormattedTextBoxComment.tooltip.style.display = "none"; - FormattedTextBoxComment.tooltip.appendChild(input); + FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput); FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => { let keep = e.target && (e.target as any).type === "checkbox" ? true : false; const textBox = FormattedTextBoxComment.textBox; if (FormattedTextBoxComment.linkDoc && !keep && textBox) { DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight")); + } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { + textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { width: 200, height: 400 }), undefined, "onRight"); } FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened; textBox && FormattedTextBoxComment.start !== undefined && textBox.setAnnotation( @@ -120,8 +129,10 @@ export class FormattedTextBoxComment { } let set = "none"; let nbef = 0; + FormattedTextBoxComment.tooltipInput.style.display = "none"; FormattedTextBoxComment.tooltip.style.width = ""; FormattedTextBoxComment.tooltip.style.height = ""; + (FormattedTextBoxComment.tooltipText as any).href = ""; // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date if (state.selection.$from) { nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); @@ -136,6 +147,7 @@ export class FormattedTextBoxComment { if (mark && child && ((nbef && naft) || !noselection)) { FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString(); set = ""; + FormattedTextBoxComment.tooltipInput.style.display = ""; } } // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. @@ -147,6 +159,7 @@ export class FormattedTextBoxComment { let mark = child && findLinkMark(child.marks); if (mark && child && nbef && naft) { FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href; + (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; docTarget && DocServer.GetRefField(docTarget).then(linkDoc => { @@ -155,9 +168,7 @@ export class FormattedTextBoxComment { const target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); try { ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); - } catch (e) { - - } + } catch (e) { } if (target) { ReactDOM.render( { @@ -114,7 +114,7 @@ export class FilterBox extends React.Component { acc[i].classList.toggle("active"); var panel = acc[i].nextElementSibling as HTMLElement; panel.style.overflow = "hidden"; - panel.style.maxHeight = null; + panel.style.maxHeight = ""; } } }); -- cgit v1.2.3-70-g09d2 From e3f06e390f98cc5b97d63fc287daff994d5fef6f Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 14 Nov 2019 23:01:49 -0500 Subject: double cliking dragger collapses menu --- src/client/util/RichTextSchema.tsx | 1 + src/client/util/TooltipTextMenu.scss | 399 ++--------------------- src/client/util/TooltipTextMenu.tsx | 610 +++++++++++++++-------------------- 3 files changed, 278 insertions(+), 732 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 1eea529d2..69f9a4c53 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -130,6 +130,7 @@ export const nodes: { [index: string]: NodeSpec } = { // } // }] }, + // :: NodeSpec An inline image (``) node. Supports `src`, // `alt`, and `href` attributes. The latter two default to the empty // string. diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 1f619b4f5..8310a0da6 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -1,6 +1,5 @@ @import "../views/globalCssVariables"; .ProseMirror-menu-dropdown-wrap { - // margin: 0 4px; display: inline-block; position: relative; } @@ -39,9 +38,10 @@ font-size: 12px; background: white; border: 1px solid rgb(223, 223, 223); - min-width: 50px; + min-width: 40px; z-index: 50000; position: absolute; + box-sizing: content-box; .ProseMirror-menu-dropdown-item { cursor: pointer; @@ -91,20 +91,22 @@ .wrapper { position: absolute; pointer-events: all; -} + display: flex; + align-items: center; + transform: translateY(-85px); -.tooltipMenu { - position: absolute; - z-index: 20000; + height: 35px; background: #323232; border-radius: 6px; - transform: translateY(-85px); + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + +} + +.tooltipMenu, .basic-tools { + z-index: 20000; pointer-events: all; - height: 35px; - // width: 650px; padding: 3px; padding-bottom: 5px; - box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); display: flex; align-items: center; @@ -203,7 +205,7 @@ } .underline svg { - margin-top: 15px; + margin-top: 13px; } .font-size-indicator { @@ -232,21 +234,19 @@ width: 1em; height: 1em; stroke-width: 3; - // stroke: greenyellow; fill: greenyellow; margin-right: 15px; } -.dragger { +.dragger-wrapper { color: #eee; - margin-left: 5px; - width: 16px; height: 22px; - display: inline-block; + padding: 0 5px; + box-sizing: content-box; cursor: grab; - .dragger-wrapper { - width: 100%; + .dragger { + width: 18px; height: 100%; display: flex; justify-content: space-evenly; @@ -259,11 +259,6 @@ } } -// .menuicon:hover + .ProseMirror-menu-dropdown-wrap .buttonSettings-dropdown, -// .menuicon-active:hover + .ProseMirror-menu-dropdown-wrap .buttonSettings-dropdown { -// background-color: black; -// } - .button-dropdown-wrapper { display: flex; align-content: center; @@ -374,360 +369,4 @@ button.colorPicker { &.active { border: 2px solid white !important; } -} - -// @import "../views/globalCssVariables"; - -// .ProseMirror-textblock-dropdown { -// min-width: 3em; -// } - -// .ProseMirror-menu { -// margin: 0 -4px; -// line-height: 1; -// } - -// .ProseMirror-tooltip .ProseMirror-menu { -// width: -webkit-fit-content; -// width: fit-content; -// white-space: pre; -// } - -// .ProseMirror-menuitem { -// margin-right: 3px; -// display: inline-block; -// z-index: 50000; -// position: relative; -// } - -// .ProseMirror-menuseparator { -// // border-right: 1px solid #ddd; -// margin-right: 3px; -// } - -// .ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu { -// font-size: 90%; -// white-space: nowrap; -// } - -// .ProseMirror-menu-dropdown { -// vertical-align: 1px; -// cursor: pointer; -// position: relative; -// padding-right: 15px; -// margin: 3px; -// background: white; -// border-radius: 3px; -// text-align: center; -// } - -// .ProseMirror-menu-dropdown-wrap { -// padding: 1px 0 1px 4px; -// display: inline-block; -// position: relative; -// } - -// .ProseMirror-menu-dropdown:after { -// content: ""; -// border-left: 4px solid transparent; -// border-right: 4px solid transparent; -// border-top: 4px solid currentColor; -// opacity: .6; -// position: absolute; -// right: 4px; -// top: calc(50% - 2px); -// } - -// .ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu { -// background: $dark-color; -// color:white; -// border: 1px solid rgb(223, 223, 223); -// padding: 2px; -// } - -// .ProseMirror-menu-dropdown-menu { -// z-index: 50000; -// min-width: 6em; -// background: white; -// position: absolute; -// } - -// .linking { -// text-align: center; -// } - -// .ProseMirror-menu-dropdown-item { -// cursor: pointer; -// padding: 2px 8px 2px 4px; -// width: auto; -// z-index: 100000; -// } - -// .ProseMirror-menu-dropdown-item:hover { -// background: white; -// } - -// .ProseMirror-menu-submenu-wrap { -// position: relative; -// margin-right: -4px; -// } - -// .ProseMirror-menu-submenu-label:after { -// content: ""; -// border-top: 4px solid transparent; -// border-bottom: 4px solid transparent; -// border-left: 4px solid currentColor; -// opacity: .6; -// position: absolute; -// right: 4px; -// top: calc(50% - 4px); -// } - -// .ProseMirror-menu-submenu { -// display: none; -// min-width: 4em; -// left: 100%; -// top: -3px; -// } - -// .ProseMirror-menu-active { -// background: #eee; -// border-radius: 4px; -// } - -// .ProseMirror-menu-active { -// background: #eee; -// border-radius: 4px; -// } - -// .ProseMirror-menu-disabled { -// opacity: .3; -// } - -// .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu { -// display: block; -// } - -// .ProseMirror-menubar { -// border-top-left-radius: inherit; -// border-top-right-radius: inherit; -// position: relative; -// min-height: 1em; -// color: white; -// padding: 10px 10px; -// top: 0; left: 0; right: 0; -// border-bottom: 1px solid silver; -// background:$dark-color; -// z-index: 10; -// -moz-box-sizing: border-box; -// box-sizing: border-box; -// overflow: visible; -// } - -// .ProseMirror-icon { -// display: inline-block; -// line-height: .8; -// vertical-align: -2px; /* Compensate for padding */ -// padding: 2px 8px; -// cursor: pointer; -// } - -// .ProseMirror-menu-disabled.ProseMirror-icon { -// cursor: default; -// } - -// .ProseMirror-icon svg { -// fill:white; -// height: 1em; -// } - -// .ProseMirror-icon span { -// vertical-align: text-top; -// } - -// .ProseMirror ul, .ProseMirror ol { -// padding-left: 30px; -// } - -// .ProseMirror blockquote { -// padding-left: 1em; -// border-left: 3px solid #eee; -// margin-left: 0; margin-right: 0; -// } - -// .ProseMirror-example-setup-style img { -// cursor: default; -// } - -// .ProseMirror-prompt { -// background: white; -// padding: 5px 10px 5px 15px; -// border: 1px solid silver; -// position: fixed; -// border-radius: 3px; -// z-index: 11; -// box-shadow: -.5px 2px 5px white(255, 255, 255, 0.2); -// } - -// .ProseMirror-prompt h5 { -// margin: 0; -// font-weight: normal; -// font-size: 100%; -// color: #444; -// } - -// .ProseMirror-prompt input[type="text"], -// .ProseMirror-prompt textarea { -// background: white; -// border: none; -// outline: none; -// } - -// .ProseMirror-prompt input[type="text"] { -// padding: 0 4px; -// } - -// .ProseMirror-prompt-close { -// position: absolute; -// left: 2px; top: 1px; -// color: #666; -// border: none; background: transparent; padding: 0; -// } - -// .ProseMirror-prompt-close:after { -// content: "✕"; -// font-size: 12px; -// } - -// .ProseMirror-invalid { -// background: #ffc; -// border: 1px solid #cc7; -// border-radius: 4px; -// padding: 5px 10px; -// position: absolute; -// min-width: 10em; -// } - -// .ProseMirror-prompt-buttons { -// margin-top: 5px; -// display: none; -// } - -// .tooltipMenu { -// position: absolute; -// z-index: 20000; -// background: #121721; -// border: 1px solid silver; -// border-radius: 15px; -// //height: 60px; -// //padding: 2px 10px; -// //margin-top: 100px; -// //-webkit-transform: translateX(-50%); -// //transform: translateX(-50%); -// transform: translateY(-85px); -// pointer-events: all; -// height: fit-content; -// width:550px; -// .ProseMirror-example-setup-style hr { -// padding: 2px 10px; -// border: none; -// margin: 1em 0; -// } - -// .ProseMirror-example-setup-style hr:after { -// content: ""; -// display: block; -// height: 1px; -// background-color: silver; -// line-height: 2px; -// } -// } - -// .tooltipExtras { -// position: absolute; -// z-index: 20000; -// background: #121721; -// border: 1px solid silver; -// border-radius: 15px; -// //height: 60px; -// //padding: 2px 10px; -// //margin-top: 100px; -// //-webkit-transform: translateX(-50%); -// //transform: translateX(-50%); -// transform: translateY(-115px); -// pointer-events: all; -// height: 25px; -// width:fit-content; -// .ProseMirror-example-setup-style hr { -// padding: 2px 10px; -// border: none; -// margin: 1em 0; -// } - -// .ProseMirror-example-setup-style hr:after { -// content: ""; -// display: block; -// height: 1px; -// background-color: silver; -// line-height: 2px; -// } -// } - -// .wrapper { -// position: absolute; -// pointer-events: all; -// } - -// .menuicon { -// display: inline-block; -// border-right: 1px solid white(0, 0, 0, 0.2); -// //color: rgb(19, 18, 18); -// color: rgb(226, 21, 21); -// line-height: 1; -// padding: 0px 2px; -// margin: 1px; -// cursor: pointer; -// text-align: center; -// min-width: 10px; - -// } -// .strong, .heading { font-weight: bold; } -// .em { font-style: italic; } -// .underline {text-decoration: underline} -// .superscript {vertical-align:super} -// .subscript { vertical-align:sub } -// .strikethrough {text-decoration-line:line-through} -// .font-size-indicator { -// font-size: 12px; -// padding-right: 0px; -// } -// .summarize{ -// color: white; -// height: 20px; -// text-align: center; -// } - -// .brush{ -// display: inline-block; -// width: 1em; -// height: 1em; -// stroke-width: 0; -// stroke: currentColor; -// fill: currentColor; -// margin-right: 15px; -// } - -// .brush-active{ -// display: inline-block; -// width: 1em; -// height: 1em; -// stroke-width: 3; -// stroke: greenyellow; -// fill: greenyellow; -// margin-right: 15px; -// } - -// .dragger{ -// color: #eee; -// margin-left: 5px; -// } \ No newline at end of file +} \ No newline at end of file diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 564d9f0df..6001f9840 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -30,9 +30,8 @@ export class TooltipTextMenu { public static Toolbar: HTMLDivElement | undefined; - // editor state + // editor state properties private view: EditorView; - // private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; private fontStyles: MarkType[] = []; private fontSizes: MarkType[] = []; private listTypes: (NodeType | any)[] = []; @@ -42,13 +41,16 @@ export class TooltipTextMenu { private _activeMarks: Mark[] = []; private _marksToDoms: Map = new Map(); private _collapsed: boolean = false; - //private link: HTMLAnchorElement; // editor doms public tooltip: HTMLElement = document.createElement("div"); private wrapper: HTMLDivElement = document.createElement("div"); // editor button doms + private colorDom?: Node; + private colorDropdownDom?: Node; + private highlightDom?: Node; + private highlightDropdownDom?: Node; private linkEditor?: HTMLDivElement; private linkText?: HTMLDivElement; private linkDrag?: HTMLImageElement; @@ -58,42 +60,32 @@ export class TooltipTextMenu { private fontSizeDom?: Node; private fontStyleDom?: Node; private listTypeBtnDom?: Node; - private colorDom?: Node; - private colorDropdownDom?: Node; - private highlightDom?: Node; - private highlightDropdownDom?: Node; - - - // private _collapseBtn?: MenuItem; - // private _brushIsEmpty: boolean = true; - + private basicTools?: HTMLElement; constructor(view: EditorView) { this.view = view; - // // replace old active menu with this - // if (TooltipTextMenuManager.Instance.activeMenu) { - // TooltipTextMenuManager.Instance.activeMenu.wrapper.remove(); - // } - // TooltipTextMenuManager.Instance.activeMenu = this; - - // initialize the tooltip - this.createTooltip(view); + // initialize the tooltip -- sets this.tooltip + this.initTooltip(view); // initialize the wrapper this.wrapper = document.createElement("div"); this.wrapper.className = "wrapper"; this.wrapper.appendChild(this.tooltip); - // positioning? + // initialize the dragger -- appends it to the wrapper + this.createDragger(); + TooltipTextMenu.Toolbar = this.wrapper; } - private async createTooltip(view: EditorView) { + private async initTooltip(view: EditorView) { // initialize tooltip dom this.tooltip = document.createElement("div"); this.tooltip.className = "tooltipMenu"; + this.basicTools = document.createElement("div"); + this.basicTools.className = "basic-tools"; // init buttons to the tooltip -- paths to svgs are obtained from fontawesome let items = [ @@ -113,12 +105,15 @@ export class TooltipTextMenu { switch (dom.title) { case "Bold": this._marksToDoms.set(schema.mark(schema.marks.strong), dom); + this.basicTools && this.basicTools.appendChild(dom.cloneNode(true)); break; case "Italic": this._marksToDoms.set(schema.mark(schema.marks.em), dom); + this.basicTools && this.basicTools.appendChild(dom.cloneNode(true)); break; case "Underline": this._marksToDoms.set(schema.mark(schema.marks.underline), dom); + this.basicTools && this.basicTools.appendChild(dom.cloneNode(true)); break; } @@ -172,19 +167,12 @@ export class TooltipTextMenu { this.tooltip.appendChild(this._brushDropdownDom); // star - // this.tooltip.appendChild(this.createLink().render(this.view).dom); this.tooltip.appendChild(this.createStar().render(this.view).dom); - // + // list types dropdown this.updateListItemDropdown(":", this.listTypeBtnDom); - // await this.updateFromDash(view, undefined, undefined); - // TooltipTextMenu.Toolbar = this.wrapper; - - // dragger - // TODO: onclick handler in drag handles collapsing - this.createDragger(); } initFontStyles() { @@ -224,13 +212,15 @@ export class TooltipTextMenu { this.listTypes = Array.from(this.listTypeToIcon.keys()); } + // creates dragger element that allows dragging and collapsing (on double click) + // of editor and appends it to the wrapper createDragger() { - const dragger = document.createElement("div"); - dragger.className = "dragger"; - let draggerWrapper = document.createElement("div"); draggerWrapper.className = "dragger-wrapper"; + let dragger = document.createElement("div"); + dragger.className = "dragger"; + let line1 = document.createElement("span"); line1.className = "dragger-line"; let line2 = document.createElement("span"); @@ -238,14 +228,72 @@ export class TooltipTextMenu { let line3 = document.createElement("span"); line3.className = "dragger-line"; - draggerWrapper.appendChild(line1); - draggerWrapper.appendChild(line2); - draggerWrapper.appendChild(line3); + dragger.appendChild(line1); + dragger.appendChild(line2); + dragger.appendChild(line3); - dragger.appendChild(draggerWrapper); + draggerWrapper.appendChild(dragger); - this.tooltip.appendChild(dragger); - this.dragElement(dragger); + this.wrapper.appendChild(draggerWrapper); + this.dragElement(draggerWrapper); + } + + dragElement(elmnt: HTMLElement) { + var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; + if (elmnt) { + // if present, the header is where you move the DIV from: + elmnt.onpointerdown = dragMouseDown; + elmnt.ondblclick = onClick; + } + const self = this; + + function dragMouseDown(e: PointerEvent) { + e = e || window.event; + //e.preventDefault(); + // get the mouse cursor position at startup: + pos3 = e.clientX; + pos4 = e.clientY; + document.onpointerup = closeDragElement; + // call a function whenever the cursor moves: + document.onpointermove = elementDrag; + } + + function onClick(e: MouseEvent) { + self._collapsed = !self._collapsed; + const children = self.wrapper.childNodes; + if (self._collapsed && children.length > 0) { + self.wrapper.removeChild(self.tooltip); + self.basicTools && self.wrapper.prepend(self.basicTools); + } + else { + self.wrapper.prepend(self.tooltip); + self.basicTools && self.wrapper.removeChild(self.basicTools); + } + } + + function elementDrag(e: PointerEvent) { + e = e || window.event; + //e.preventDefault(); + // calculate the new cursor position: + pos1 = pos3 - e.clientX; + pos2 = pos4 - e.clientY; + pos3 = e.clientX; + pos4 = e.clientY; + // set the element's new position: + // elmnt.style.top = (elmnt.offsetTop - pos2) + "px"; + // elmnt.style.left = (elmnt.offsetLeft - pos1) + "px"; + + self.wrapper.style.top = (self.wrapper.offsetTop - pos2) + "px"; + self.wrapper.style.left = (self.wrapper.offsetLeft - pos1) + "px"; + } + + function closeDragElement() { + // stop moving when mouse button is released: + document.onpointerup = null; + document.onpointermove = null; + //self.highlightSearchTerms(self.state, ["hello"]); + //FormattedTextBox.Instance.unhighlightSearchTerms(); + } } //label of dropdown will change to given label @@ -261,7 +309,7 @@ export class TooltipTextMenu { let newfontSizeDom = (new Dropdown(cut(fontSizeBtns), { label: label, - css: "color:black; min-width: 60px; padding-left: 5px; margin-right: 0;" + css: "color:black; min-width: 60px;" }) as MenuItem).render(this.view).dom; if (this.fontSizeDom) { this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom); } else { @@ -270,8 +318,6 @@ export class TooltipTextMenu { this.fontSizeDom = newfontSizeDom; } - // Make the DIV element draggable - //label of dropdown will change to given label updateFontStyleDropdown(label: string) { //filtering function - might be unecessary @@ -285,7 +331,7 @@ export class TooltipTextMenu { let newfontStyleDom = (new Dropdown(cut(fontBtns), { label: label, - css: "color:black; width: 125px; margin-left: -3px; padding-left: 2px;" + css: "color:black; width: 125px;" }) as MenuItem).render(this.view).dom; if (this.fontStyleDom) { this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom); } else { @@ -299,11 +345,7 @@ export class TooltipTextMenu { if (!this.linkEditor || !this.linkText) { this.linkEditor = document.createElement("div"); this.linkEditor.className = "ProseMirror-icon menuicon"; - // this.linkEditor.style.color = "black"; this.linkText = document.createElement("div"); - // this.linkText.style.cssFloat = "left"; - // this.linkText.style.marginRight = "5px"; - // this.linkText.style.marginLeft = "5px"; this.linkText.setAttribute("contenteditable", "true"); this.linkText.style.whiteSpace = "nowrap"; this.linkText.style.width = "150px"; @@ -391,13 +433,11 @@ export class TooltipTextMenu { e.preventDefault(); } }; - // this.tooltip.appendChild(this.linkEditor); } async getTextLinkTargetTitle() { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); - // let href = link!.attrs.href; if (link) { let href = link.attrs.href; if (href) { @@ -445,7 +485,6 @@ export class TooltipTextMenu { input.type = "text"; input.placeholder = "Enter URL"; - console.log(targetTitle); if (targetTitle) input.value = targetTitle; input.onclick = (e: MouseEvent) => { input.select(); @@ -514,62 +553,6 @@ export class TooltipTextMenu { return linkDropdown; } - dragElement(elmnt: HTMLElement) { - var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; - if (elmnt) { - // if present, the header is where you move the DIV from: - elmnt.onpointerdown = dragMouseDown; - elmnt.ondblclick = onClick; - } - const self = this; - - function dragMouseDown(e: PointerEvent) { - e = e || window.event; - //e.preventDefault(); - // get the mouse cursor position at startup: - pos3 = e.clientX; - pos4 = e.clientY; - document.onpointerup = closeDragElement; - // call a function whenever the cursor moves: - document.onpointermove = elementDrag; - } - - function onClick(e: MouseEvent) { - self._collapsed = !self._collapsed; - const children = self.wrapper.childNodes; - if (self._collapsed && children.length > 1) { - self.wrapper.removeChild(self.tooltip); - } - else { - self.wrapper.appendChild(self.tooltip); - } - } - - function elementDrag(e: PointerEvent) { - e = e || window.event; - //e.preventDefault(); - // calculate the new cursor position: - pos1 = pos3 - e.clientX; - pos2 = pos4 - e.clientY; - pos3 = e.clientX; - pos4 = e.clientY; - // set the element's new position: - // elmnt.style.top = (elmnt.offsetTop - pos2) + "px"; - // elmnt.style.left = (elmnt.offsetLeft - pos1) + "px"; - - self.wrapper.style.top = (self.wrapper.offsetTop - pos2) + "px"; - self.wrapper.style.left = (self.wrapper.offsetLeft - pos1) + "px"; - } - - function closeDragElement() { - // stop moving when mouse button is released: - document.onpointerup = null; - document.onpointermove = null; - //self.highlightSearchTerms(self.state, ["hello"]); - //FormattedTextBox.Instance.unhighlightSearchTerms(); - } - } - // makeLinkWithState = (state: EditorState, target: string, location: string) => { // let link = state.schema.mark(state.schema.marks.link, { href: target, location: location }); // } @@ -616,10 +599,70 @@ export class TooltipTextMenu { } } } + } - + deleteLinkItem() { + const icon = { + height: 16, width: 16, + path: "M15.898,4.045c-0.271-0.272-0.713-0.272-0.986,0l-4.71,4.711L5.493,4.045c-0.272-0.272-0.714-0.272-0.986,0s-0.272,0.714,0,0.986l4.709,4.711l-4.71,4.711c-0.272,0.271-0.272,0.713,0,0.986c0.136,0.136,0.314,0.203,0.492,0.203c0.179,0,0.357-0.067,0.493-0.203l4.711-4.711l4.71,4.711c0.137,0.136,0.314,0.203,0.494,0.203c0.178,0,0.355-0.067,0.492-0.203c0.273-0.273,0.273-0.715,0-0.986l-4.711-4.711l4.711-4.711C16.172,4.759,16.172,4.317,15.898,4.045z" + }; + return new MenuItem({ + title: "Delete Link", + label: "X", + icon: icon, + css: "color: red", + class: "summarize", + execEvent: "", + run: (state, dispatch) => { + this.deleteLink(); + } + }); } + createLink() { + let markType = schema.marks.link; + return new MenuItem({ + title: "Add or remove link", + label: "Add or remove link", + execEvent: "", + icon: icons.link, + css: "color:white;", + class: "menuicon", + enable(state) { return !state.selection.empty; }, + run: (state, dispatch, view) => { + // to remove link + let curLink = ""; + if (this.markActive(state, markType)) { + + let { from, $from, to, empty } = state.selection; + let node = state.doc.nodeAt(from); + node && node.marks.map(m => { + m.type === markType && (curLink = m.attrs.href); + }); + //toggleMark(markType)(state, dispatch); + //return true; + } + // to create link + openPrompt({ + title: "Create a link", + fields: { + href: new TextField({ + value: curLink, + label: "Link Target", + required: true + }), + title: new TextField({ label: "Title" }) + }, + callback(attrs: any) { + toggleMark(markType, attrs)(view.state, view.dispatch); + view.focus(); + }, + flyout_top: 0, + flyout_left: 0 + }); + } + }); + } //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown updateListItemDropdown(label: string, listTypeBtn: any) { @@ -644,108 +687,6 @@ export class TooltipTextMenu { return listTypeBtn; } - //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text - changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => { - let { $cursor, ranges } = view.state.selection as TextSelection; - let state = view.state; - let dispatch = view.dispatch; - - //remove all other active font marks - fontMarks.forEach((type) => { - if (dispatch) { - if ($cursor) { - if (type.isInSet(state.storedMarks || $cursor.marks())) { - dispatch(state.tr.removeStoredMark(type)); - } - } else { - let has = false; - for (let i = 0; !has && i < ranges.length; i++) { - let { $from, $to } = ranges[i]; - has = state.doc.rangeHasMark($from.pos, $to.pos, type); - } - for (let i of ranges) { - if (has) { - toggleMark(type)(view.state, view.dispatch, view); - } - } - } - } - }); - - if (markType) { - // fontsize - if (markType.name[0] === 'p') { - let size = this.fontSizeToNum.get(markType); - if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } - if (this.editorProps) { - let ruleProvider = this.editorProps.ruleProvider; - let heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleSize_" + heading] = size; - } - } - } - else { - let fontName = this.fontStylesToName.get(markType); - if (fontName) { this.updateFontStyleDropdown(fontName); } - if (this.editorProps) { - let ruleProvider = this.editorProps.ruleProvider; - let heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleFont_" + heading] = fontName; - } - } - } - //actually apply font - if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { - let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, - { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: markType.name, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema); - view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); - } - else toggleMark(markType)(view.state, view.dispatch, view); - } - } - - //remove all node typeand apply the passed-in one to the selected text - changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => { - //remove oldif (nodeType) { //add new - if (nodeType === schema.nodes.bullet_list) { - wrapInList(nodeType)(view.state, view.dispatch); - } else { - var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks()); - if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { - let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - - view.dispatch(tx2); - })) { - let tx2 = view.state.tr; - let tx3 = nodeType ? updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle) : tx2; - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - - view.dispatch(tx3); - } - } - } - - //makes a button for the drop down FOR MARKS - //css is the style you want applied to the button - dropdownMarkBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) { - return new MenuItem({ - title: "", - label: label, - execEvent: "", - class: "menuicon", - css: css, - enable() { return true; }, - run() { - changeToMarkInGroup(markType, view, groupMarks); - } - }); - } - createStar() { return new MenuItem({ title: "Summarize", @@ -772,6 +713,17 @@ export class TooltipTextMenu { return true; } + public static insertComment(state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + let mark = state.schema.marks.highlight.create(); + let tr = state.tr; + tr.addMark(state.selection.from, state.selection.to, mark); + let content = tr.selection.content(); + let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); + dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); + return true; + } + createHighlightTool() { return new MenuItem({ title: "Highlight", @@ -975,24 +927,6 @@ export class TooltipTextMenu { return colorDropdown; } - deleteLinkItem() { - const icon = { - height: 16, width: 16, - path: "M15.898,4.045c-0.271-0.272-0.713-0.272-0.986,0l-4.71,4.711L5.493,4.045c-0.272-0.272-0.714-0.272-0.986,0s-0.272,0.714,0,0.986l4.709,4.711l-4.71,4.711c-0.272,0.271-0.272,0.713,0,0.986c0.136,0.136,0.314,0.203,0.492,0.203c0.179,0,0.357-0.067,0.493-0.203l4.711-4.711l4.71,4.711c0.137,0.136,0.314,0.203,0.494,0.203c0.178,0,0.355-0.067,0.492-0.203c0.273-0.273,0.273-0.715,0-0.986l-4.711-4.711l4.711-4.711C16.172,4.759,16.172,4.317,15.898,4.045z" - }; - return new MenuItem({ - title: "Delete Link", - label: "X", - icon: icon, - css: "color: red", - class: "summarize", - execEvent: "", - run: (state, dispatch) => { - this.deleteLink(); - } - }); - } - createBrush(active: boolean = false) { const icon = { height: 32, width: 32, @@ -1020,8 +954,6 @@ export class TooltipTextMenu { }); } - // selectionchanged event handler - brush_function(state: EditorState, dispatch: any) { if (TooltipTextMenuManager.Instance._brushIsEmpty) { const selected_marks = this.getMarksInSelection(this.view.state); @@ -1055,8 +987,6 @@ export class TooltipTextMenu { } } } - - } createBrushDropdown(active: boolean = false) { @@ -1120,88 +1050,106 @@ export class TooltipTextMenu { return brushDom; } - // createCollapse() { - // this._collapseBtn = new MenuItem({ - // title: "Collapse", - // //label: "Collapse", - // icon: icons.join, - // execEvent: "", - // css: "color:white;", - // class: "summarize", - // run: () => { - // this.collapseToolTip(); - // } - // }); - // } - // collapseToolTip() { - // if (this._collapseBtn) { - // if (this._collapseBtn.spec.title === "Collapse") { - // // const newcollapseBtn = new MenuItem({ - // // title: "Expand", - // // icon: icons.join, - // // execEvent: "", - // // css: "color:white;", - // // class: "summarize", - // // run: (state, dispatch, view) => { - // // this.collapseToolTip(); - // // } - // // }); - // // this.tooltip.replaceChild(newcollapseBtn.render(this.view).dom, this._collapseBtn.render(this.view).dom); - // // this._collapseBtn = newcollapseBtn; - // this.tooltip.style.width = "30px"; - // this._collapseBtn.spec.title = "Expand"; - // this._collapseBtn.render(this.view); - // } - // else { - // this._collapseBtn.spec.title = "Collapse"; - // this.tooltip.style.width = "550px"; - // this._collapseBtn.render(this.view); - // } - // } - // } - createLink() { - let markType = schema.marks.link; - return new MenuItem({ - title: "Add or remove link", - label: "Add or remove link", - execEvent: "", - icon: icons.link, - css: "color:white;", - class: "menuicon", - enable(state) { return !state.selection.empty; }, - run: (state, dispatch, view) => { - // to remove link - let curLink = ""; - if (this.markActive(state, markType)) { + //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text + changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => { + let { $cursor, ranges } = view.state.selection as TextSelection; + let state = view.state; + let dispatch = view.dispatch; - let { from, $from, to, empty } = state.selection; - let node = state.doc.nodeAt(from); - node && node.marks.map(m => { - m.type === markType && (curLink = m.attrs.href); - }); - //toggleMark(markType)(state, dispatch); - //return true; + //remove all other active font marks + fontMarks.forEach((type) => { + if (dispatch) { + if ($cursor) { + if (type.isInSet(state.storedMarks || $cursor.marks())) { + dispatch(state.tr.removeStoredMark(type)); + } + } else { + let has = false; + for (let i = 0; !has && i < ranges.length; i++) { + let { $from, $to } = ranges[i]; + has = state.doc.rangeHasMark($from.pos, $to.pos, type); + } + for (let i of ranges) { + if (has) { + toggleMark(type)(view.state, view.dispatch, view); + } + } } - // to create link - openPrompt({ - title: "Create a link", - fields: { - href: new TextField({ - value: curLink, - label: "Link Target", - required: true - }), - title: new TextField({ label: "Title" }) - }, - callback(attrs: any) { - toggleMark(markType, attrs)(view.state, view.dispatch); - view.focus(); - }, - flyout_top: 0, - flyout_left: 0 - }); + } + }); + + if (markType) { + // fontsize + if (markType.name[0] === 'p') { + let size = this.fontSizeToNum.get(markType); + if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } + if (this.editorProps) { + let ruleProvider = this.editorProps.ruleProvider; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleSize_" + heading] = size; + } + } + } + else { + let fontName = this.fontStylesToName.get(markType); + if (fontName) { this.updateFontStyleDropdown(fontName); } + if (this.editorProps) { + let ruleProvider = this.editorProps.ruleProvider; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleFont_" + heading] = fontName; + } + } + } + //actually apply font + if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { + let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, + { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: markType.name, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema); + view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); + } + else toggleMark(markType)(view.state, view.dispatch, view); + } + } + + //remove all node typeand apply the passed-in one to the selected text + changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => { + //remove oldif (nodeType) { //add new + if (nodeType === schema.nodes.bullet_list) { + wrapInList(nodeType)(view.state, view.dispatch); + } else { + var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks()); + if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { + let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + + view.dispatch(tx2); + })) { + let tx2 = view.state.tr; + let tx3 = nodeType ? updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle) : tx2; + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + + view.dispatch(tx3); + } + } + } + + //makes a button for the drop down FOR MARKS + //css is the style you want applied to the button + dropdownMarkBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) { + return new MenuItem({ + title: "", + label: label, + execEvent: "", + class: "dropdown-item", + css: css, + enable() { return true; }, + run() { + changeToMarkInGroup(markType, view, groupMarks); } }); } @@ -1213,7 +1161,7 @@ export class TooltipTextMenu { title: "", label: label, execEvent: "", - class: "menuicon", + class: "dropdown-item", css: css, enable() { return true; }, run() { @@ -1390,35 +1338,12 @@ export class TooltipTextMenu { update_mark_doms() { this.reset_mark_doms(); - let foundlink = false; - // let children = this.extras.childNodes; this._activeMarks.forEach((mark) => { if (this._marksToDoms.has(mark)) { let dom = this._marksToDoms.get(mark); if (dom) dom.style.color = "greenyellow"; } - // if (children.length > 1) { - // foundlink = true; - // } - // if (mark.type.name === "link" && children.length === 1) { - // // let del = document.createElement("button"); - // // del.textContent = "X"; - // // del.style.color = "red"; - // // del.style.height = "10px"; - // // del.style.width = "10px"; - // // del.style.marginLeft = "5px"; - // // del.onclick = this.deleteLink; - // // this.extras.appendChild(del); - // let del = this.deleteLinkItem().render(this.view).dom; - // this.extras.appendChild(del); - // foundlink = true; - // } }); - // if (!foundlink) { - // if (children.length > 1) { - // this.extras.removeChild(children[1]); - // } - // } // keeps brush tool highlighted if active when switching between textboxes if (!TooltipTextMenuManager.Instance._brushIsEmpty) { @@ -1535,30 +1460,11 @@ class TooltipTextMenuManager { return TooltipTextMenuManager._instance; } - // private pinnedToUnpinned() { - // let position = MainOverlayTextBox.Instance.position; - - // this.unpinnedX = this.pinnedX - position[0]; - // this.unpinnedY = this.pinnedY - position[1]; - // } - - // private unpinnedToPinned() { - // let position = MainOverlayTextBox.Instance.position; - - // this.pinnedX = position[0] + this.unpinnedX; - // this.pinnedY = position[1] + this.unpinnedY; - // } - public get isPinned() { return this._isPinned; } public toggleIsPinned() { - // if (this._isPinned) { - // this.pinnedToUnpinned(); - // } else { - // this.unpinnedToPinned(); - // } this._isPinned = !this._isPinned; } } -- cgit v1.2.3-70-g09d2 From 1f1707c8756c858e45e1f89cd0ff035231e15225 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 15 Nov 2019 13:52:23 -0500 Subject: fixed errors --- .vscode/settings.json | 2 +- src/client/util/TooltipTextMenu.tsx | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'src/client/util') diff --git a/.vscode/settings.json b/.vscode/settings.json index c999af8b8..0b45c3f46 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,5 @@ "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true, "search.usePCRE2": true, - "typescript.tsdk": "node_modules\\typescript\\lib" + "typescript.tsdk": "node_modules/typescript/lib" } \ No newline at end of file diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 78917c779..0dc63668b 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -33,12 +33,10 @@ export class TooltipTextMenu { // editor state properties private view: EditorView; private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; - - private fontStyles: MarkType[] = []; - private fontSizes: MarkType[] = []; + + private fontStyles: Mark[] = []; + private fontSizes: Mark[] = []; private listTypes: (NodeType | any)[] = []; - private fontSizeToNum: Map = new Map(); - private fontStylesToName: Map = new Map(); private listTypeToIcon: Map = new Map(); private _activeMarks: Mark[] = []; private _marksToDoms: Map = new Map(); -- cgit v1.2.3-70-g09d2 From 79c66275c60001e6d7aa997d9802ec0fed893205 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 15 Nov 2019 15:03:08 -0500 Subject: fixed toolbar disappearing with multiple text boxes. --- src/client/util/TooltipTextMenu.tsx | 6 ++---- src/client/views/DocumentDecorations.tsx | 4 ++-- src/client/views/nodes/FormattedTextBox.tsx | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 0dc63668b..14f8b2bd4 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1,6 +1,4 @@ -import { library, dom } from '@fortawesome/fontawesome-svg-core'; -import { faListUl } from '@fortawesome/free-solid-svg-icons'; -import { action, observable } from "mobx"; +import { action } from "mobx"; import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model"; import { wrapInList } from 'prosemirror-schema-list'; @@ -1482,7 +1480,7 @@ export class TooltipTextMenu { } destroy() { - this.wrapper.remove(); + // this.wrapper.remove(); } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 49921170f..890c32bcb 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -548,8 +548,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } } public showTextBar = () => { - if (this.TextBar) { - TooltipTextMenu.Toolbar && Array.from(this.TextBar.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1 && this.TextBar.appendChild(TooltipTextMenu.Toolbar); + if (this.TextBar && TooltipTextMenu.Toolbar && Array.from(this.TextBar.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1) { + this.TextBar.appendChild(TooltipTextMenu.Toolbar); } } render() { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0a535045b..35212b732 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -17,7 +17,7 @@ import { Copy, Id } from '../../../new_fields/FieldSymbols'; import { RichTextField } from "../../../new_fields/RichTextField"; import { RichTextUtils } from '../../../new_fields/RichTextUtils'; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnOne } from '../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../DocServer"; -- cgit v1.2.3-70-g09d2 From 2ab22e64e9b95e6b76566c493ae90f5951475e72 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 15 Nov 2019 15:11:26 -0500 Subject: fixed naming of 'marker' mark --- src/client/util/RichTextSchema.tsx | 2 +- src/client/util/TooltipTextMenu.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 0e8c9f14c..0a717dff1 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -326,7 +326,7 @@ export const marks: { [index: string]: MarkSpec } = { } }, - highlight2: { + marker: { attrs: { highlight: { default: "transparent" } }, diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 14f8b2bd4..5136089b3 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -735,7 +735,7 @@ export class TooltipTextMenu { public static insertHighlight(color: String, state: EditorState, dispatch: any) { if (state.selection.empty) return false; - let highlightMark = state.schema.mark(state.schema.marks.highlight2, { highlight: color }); + let highlightMark = state.schema.mark(state.schema.marks.marker, { highlight: color }); dispatch(state.tr.addMark(state.selection.from, state.selection.to, highlightMark)); } @@ -1223,7 +1223,7 @@ export class TooltipTextMenu { }); } - markActive = function (state: EditorState, type: MarkType>) { + markActive = function(state: EditorState, type: MarkType>) { let { from, $from, to, empty } = state.selection; if (empty) return type.isInSet(state.storedMarks || $from.marks()); else return state.doc.rangeHasMark(from, to, type); -- cgit v1.2.3-70-g09d2