aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--deploy/index.html281
-rw-r--r--package-lock.json837
-rw-r--r--package.json10
-rw-r--r--src/Utils.ts3
-rw-r--r--src/client/DocServer.ts9
-rw-r--r--src/client/Network.ts11
-rw-r--r--src/client/documents/DocumentTypes.ts3
-rw-r--r--src/client/documents/Documents.ts82
-rw-r--r--src/client/goldenLayout.js15
-rw-r--r--src/client/util/BranchingTrailManager.tsx137
-rw-r--r--src/client/util/CurrentUserUtils.ts97
-rw-r--r--src/client/util/DocumentManager.ts12
-rw-r--r--src/client/util/DragManager.ts22
-rw-r--r--src/client/util/InteractionUtils.tsx5
-rw-r--r--src/client/util/LinkFollower.ts24
-rw-r--r--src/client/util/LinkManager.ts11
-rw-r--r--src/client/util/RTFMarkup.tsx4
-rw-r--r--src/client/util/SearchUtil.ts4
-rw-r--r--src/client/util/SelectionManager.ts5
-rw-r--r--src/client/util/ServerStats.tsx6
-rw-r--r--src/client/util/SettingsManager.tsx50
-rw-r--r--src/client/util/reportManager/ReportManager.tsx30
-rw-r--r--src/client/util/reportManager/reportManagerUtils.ts7
-rw-r--r--src/client/views/AntimodeMenu.tsx7
-rw-r--r--src/client/views/ContextMenu.tsx7
-rw-r--r--src/client/views/DashboardView.tsx160
-rw-r--r--src/client/views/DocComponent.tsx5
-rw-r--r--src/client/views/DocumentButtonBar.tsx2
-rw-r--r--src/client/views/DocumentDecorations.tsx36
-rw-r--r--src/client/views/EditableView.scss4
-rw-r--r--src/client/views/EditableView.tsx40
-rw-r--r--src/client/views/FilterPanel.tsx59
-rw-r--r--src/client/views/InkTangentHandles.tsx1
-rw-r--r--src/client/views/InkTranscription.tsx5
-rw-r--r--src/client/views/InkingStroke.tsx35
-rw-r--r--src/client/views/LightboxView.scss11
-rw-r--r--src/client/views/LightboxView.tsx106
-rw-r--r--src/client/views/Main.tsx2
-rw-r--r--src/client/views/MainView.scss96
-rw-r--r--src/client/views/MainView.tsx47
-rw-r--r--src/client/views/MainViewModal.tsx3
-rw-r--r--src/client/views/MarqueeAnnotator.tsx6
-rw-r--r--src/client/views/OverlayView.tsx16
-rw-r--r--src/client/views/PreviewCursor.tsx1
-rw-r--r--src/client/views/PropertiesButtons.tsx20
-rw-r--r--src/client/views/PropertiesDocBacklinksSelector.tsx9
-rw-r--r--src/client/views/PropertiesSection.tsx13
-rw-r--r--src/client/views/PropertiesView.scss9
-rw-r--r--src/client/views/PropertiesView.tsx318
-rw-r--r--src/client/views/ScriptingRepl.tsx23
-rw-r--r--src/client/views/SidebarAnnos.tsx31
-rw-r--r--src/client/views/StyleProvider.scss9
-rw-r--r--src/client/views/StyleProvider.tsx95
-rw-r--r--src/client/views/UndoStack.tsx84
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.scss7
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx34
-rw-r--r--src/client/views/collections/CollectionCarouselView.scss23
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx20
-rw-r--r--src/client/views/collections/CollectionDockingView.scss391
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx58
-rw-r--r--src/client/views/collections/CollectionMenu.tsx9
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx36
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewColumn.tsx10
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewDivider.tsx11
-rw-r--r--src/client/views/collections/CollectionPileView.tsx11
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx47
-rw-r--r--src/client/views/collections/CollectionStackingView.scss9
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx14
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx20
-rw-r--r--src/client/views/collections/CollectionSubView.tsx12
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx4
-rw-r--r--src/client/views/collections/CollectionView.tsx4
-rw-r--r--src/client/views/collections/TabDocView.tsx179
-rw-r--r--src/client/views/collections/TreeView.scss25
-rw-r--r--src/client/views/collections/TreeView.tsx87
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx24
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss9
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx134
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx38
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx4
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx12
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx16
-rw-r--r--src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx74
-rw-r--r--src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx74
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss28
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx32
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx54
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx30
-rw-r--r--src/client/views/global/globalCssVariables.scss2
-rw-r--r--src/client/views/global/globalScripts.ts12
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx17
-rw-r--r--src/client/views/nodes/AudioBox.tsx82
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx1
-rw-r--r--src/client/views/nodes/ComparisonBox.scss2
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx31
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.scss7
-rw-r--r--src/client/views/nodes/DataVizBox/components/Chart.scss25
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.scss22
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx2
-rw-r--r--src/client/views/nodes/DocumentIcon.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx77
-rw-r--r--src/client/views/nodes/FieldView.tsx1
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx115
-rw-r--r--src/client/views/nodes/ImageBox.tsx19
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx2
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx12
-rw-r--r--src/client/views/nodes/LoadingBox.scss40
-rw-r--r--src/client/views/nodes/LoadingBox.tsx14
-rw-r--r--src/client/views/nodes/MapBox/MapAnchorMenu.tsx115
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx85
-rw-r--r--src/client/views/nodes/MapBox/MapBox2.tsx4
-rw-r--r--src/client/views/nodes/PDFBox.tsx16
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingBox.tsx221
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingView.tsx1
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx19
-rw-r--r--src/client/views/nodes/SliderBox.scss19
-rw-r--r--src/client/views/nodes/SliderBox.tsx103
-rw-r--r--src/client/views/nodes/VideoBox.scss2
-rw-r--r--src/client/views/nodes/VideoBox.tsx29
-rw-r--r--src/client/views/nodes/WebBox.scss1
-rw-r--r--src/client/views/nodes/WebBox.tsx148
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx3
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx147
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx29
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts7
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts2
-rw-r--r--src/client/views/nodes/trails/PresBox.scss15
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx270
-rw-r--r--src/client/views/nodes/trails/PresElementBox.scss4
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx60
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx139
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx1
-rw-r--r--src/client/views/pdf/PDFViewer.tsx13
-rw-r--r--src/client/views/search/SearchBox.tsx4
-rw-r--r--src/client/views/selectedDoc/SelectedDocView.tsx61
-rw-r--r--src/client/views/topbar/TopBar.tsx63
-rw-r--r--src/fields/Doc.ts72
-rw-r--r--src/fields/DocSymbols.ts3
-rw-r--r--src/mobile/MobileInterface.tsx5
-rw-r--r--src/server/ApiManagers/UploadManager.ts48
-rw-r--r--src/server/DashUploadUtils.ts44
-rw-r--r--src/server/server_Initialization.ts83
143 files changed, 4111 insertions, 2677 deletions
diff --git a/deploy/index.html b/deploy/index.html
index c8ad2a8d4..b345e193f 100644
--- a/deploy/index.html
+++ b/deploy/index.html
@@ -1,160 +1,161 @@
-<html style="overflow: hidden;">
+<html style="overflow: hidden">
+ <head>
+ <title>Dash</title>
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+ <link
+ href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&family=Lato:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&family=Nunito:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;0,1000;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900;1,1000&family=Roboto+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap"
+ rel="stylesheet"
+ />
+ <link rel="shortcut icon" type="image/jpg" href="./assets/favicon.png" />
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous" />
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/3.3.1/typescript.min.js"></script>
-<head>
- <title>Dash</title>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link
- href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&family=Lato:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&family=Nunito:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;0,1000;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900;1,1000&family=Roboto+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap"
- rel="stylesheet">
- <link rel="shortcut icon" type="image/jpg" href="./assets/favicon.png" />
- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
- integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
- <script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/3.3.1/typescript.min.js"></script>
-
- <script>
- function getCookie(cname) {
- var name = cname + "=";
- var ca = document.cookie.split(';');
- for (var i = 0; i < ca.length; i++) {
- var c = ca[i];
- while (c.charAt(0) == ' ') {
- c = c.substring(1);
- }
- if (c.indexOf(name) == 0) {
- return Number(c.substring(name.length, c.length));
+ <script>
+ function getCookie(cname) {
+ var name = cname + '=';
+ var ca = document.cookie.split(';');
+ for (var i = 0; i < ca.length; i++) {
+ var c = ca[i];
+ while (c.charAt(0) == ' ') {
+ c = c.substring(1);
+ }
+ if (c.indexOf(name) == 0) {
+ return Number(c.substring(name.length, c.length));
+ }
}
+ return 3000;
+ }
+ </script>
+ <style>
+ .dash-loader {
+ display: flex;
+ align-content: center;
+ justify-content: center;
+ background-color: #bdddf5;
+ transition: 0.3s;
+ z-index: 10;
+ z-index: 10;
+ width: 100%;
+ height: 100%;
}
- return 3000;
- }
- </script>
- <style>
- .dash-loader {
- display: flex;
- align-content: center;
- justify-content: center;
- background-color: #BDDDF5;
- transition: .3s;
- z-index: 10;
- z-index: 10;
- width: 100%;
- height: 100%;
- }
-
- .dash-loader-container {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- align-content: center;
- }
-
- .dash-progress-bar {
- width: 200px;
- height: 5px;
- align-self: center;
- margin-top: 20px;
- background-color: #ececec;
- border-radius: 5px;
- overflow: hidden;
- }
-
- .dash-progress {
- width: 0%;
- height: 20px;
- background-color: #4476F7;
- transition: 0.1s;
- }
-
- .dash-animation-container {
- width: fit-content;
- height: fit-content;
- padding: 20px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 100%;
- background-color: #4476F7;
- justify-self: center;
- }
- .dash-loader-text {
- font-size: 15px;
- font-family: "Roboto";
- font-weight: 400;
- text-align: center;
- color: #4476F7;
- user-select: none;
- -webkit-user-select: none;
- }
+ .dash-loader-container {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ align-content: center;
+ }
- .dash-d-path {
- stroke-dasharray: 1000;
- stroke-dashoffset: 1000;
- animation: dash-d-path 3s linear infinite;
- }
+ .dash-progress-bar {
+ width: 200px;
+ height: 5px;
+ align-self: center;
+ margin-top: 20px;
+ background-color: #ececec;
+ border-radius: 5px;
+ overflow: hidden;
+ }
- @keyframes dash-d-path {
- 0% {
- stroke-dashoffset: 1000;
+ .dash-progress {
+ width: 0%;
+ height: 20px;
+ background-color: #4476f7;
+ transition: 0.1s;
}
- 20% {
- stroke-dashoffset: 0;
+ .dash-animation-container {
+ width: fit-content;
+ height: fit-content;
+ padding: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 100%;
+ background-color: #4476f7;
+ justify-self: center;
}
- 70% {
- stroke-dashoffset: 0;
+ .dash-loader-text {
+ font-size: 15px;
+ font-family: 'Roboto';
+ font-weight: 400;
+ text-align: center;
+ color: #4476f7;
+ user-select: none;
+ -webkit-user-select: none;
}
- 90% {
+ .dash-d-path {
+ stroke-dasharray: 1000;
stroke-dashoffset: 1000;
+ animation: dash-d-path 3s linear infinite;
}
- 100% {
- stroke-dashoffset: 1000;
+ @keyframes dash-d-path {
+ 0% {
+ stroke-dashoffset: 1000;
+ }
+
+ 20% {
+ stroke-dashoffset: 0;
+ }
+
+ 70% {
+ stroke-dashoffset: 0;
+ }
+
+ 90% {
+ stroke-dashoffset: 1000;
+ }
+
+ 100% {
+ stroke-dashoffset: 1000;
+ }
}
- }
- </style>
-</head>
+ </style>
+ </head>
-<body style="display:flex" id="dash-body">
- <div class="dash-loader" id="loader">
- <div class="dash-loader-container">
- <div class="dash-animation-container">
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100px"
- height="100px" viewBox="0 0 250 350">
- <path class="dash-d-path" fill-rule="evenodd" stroke="#ececec" stroke-width="30px"
- stroke-linecap="butt" stroke-linejoin="miter" fill="none"
- d="M196.500,18.500 C195.888,79.462 194.655,160.273 197.500,195.500 C198.117,203.144 199.664,225.773 189.500,246.500 C183.949,257.819 175.192,268.535 163.500,277.500 C147.404,289.842 133.006,294.212 121.500,295.500 C106.618,297.166 92.057,294.673 79.500,288.500 C68.970,283.324 58.384,274.780 49.500,261.500 C39.958,247.237 35.188,230.375 35.500,213.500 C35.760,199.463 39.572,185.692 46.500,173.500 C55.433,157.780 65.945,148.829 75.500,143.500 C85.352,138.005 98.187,134.141 114.500,134.500 C128.982,134.819 143.177,139.076 155.500,146.500 C186.160,164.973 196.208,196.242 197.500,216.500 C197.663,219.061 197.578,222.985 197.500,226.500 C197.092,244.967 195.719,262.030 195.500,280.500 C195.460,283.898 195.500,293.783 195.500,300.500 C195.500,304.834 195.500,309.189 195.500,313.500 " />
- </svg>
- </div>
- <div class="dash-progress-bar">
- <div id="dash-progress" class="dash-progress">
+ <body style="display: flex" id="dash-body">
+ <div class="dash-loader" id="loader">
+ <div class="dash-loader-container">
+ <div class="dash-animation-container">
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100px" height="100px" viewBox="0 0 250 350">
+ <path
+ class="dash-d-path"
+ fill-rule="evenodd"
+ stroke="#ececec"
+ stroke-width="30px"
+ stroke-linecap="butt"
+ stroke-linejoin="miter"
+ fill="none"
+ d="M196.500,18.500 C195.888,79.462 194.655,160.273 197.500,195.500 C198.117,203.144 199.664,225.773 189.500,246.500 C183.949,257.819 175.192,268.535 163.500,277.500 C147.404,289.842 133.006,294.212 121.500,295.500 C106.618,297.166 92.057,294.673 79.500,288.500 C68.970,283.324 58.384,274.780 49.500,261.500 C39.958,247.237 35.188,230.375 35.500,213.500 C35.760,199.463 39.572,185.692 46.500,173.500 C55.433,157.780 65.945,148.829 75.500,143.500 C85.352,138.005 98.187,134.141 114.500,134.500 C128.982,134.819 143.177,139.076 155.500,146.500 C186.160,164.973 196.208,196.242 197.500,216.500 C197.663,219.061 197.578,222.985 197.500,226.500 C197.092,244.967 195.719,262.030 195.500,280.500 C195.460,283.898 195.500,293.783 195.500,300.500 C195.500,304.834 195.500,309.189 195.500,313.500 "
+ />
+ </svg>
</div>
- <script>
- let load = getCookie("loadtime");
- document.startLoad = Date.now();
- console.log("Last Load = " + load);
- let dashmesg = (width, msg) => {
- if (document.getElementById("dash-progress")) document.getElementById("dash-progress").style.width = width
- if (document.getElementById("dash-progress")) document.getElementById("dash-loader-text").innerHTML = msg
- }
- // setTimeout(() => dashmesg("10%", "Loading Dash..."), load / 10);
- // setTimeout(() => dashmesg("33%", "Preparing dashboards..."), load / 3);
- // setTimeout(() => dashmesg("50%", "Initializing scripts..."), load / 2);
- // setTimeout(() => dashmesg("75%", "Fetching documents..."), load / 4 * 3);
- // setTimeout(() => dashmesg("100%", "Finalising setup..."), load);
- </script>
- </div>
- <div id="dash-loader-text" class="dash-loader-text">
- Loading Dash...
+ <div class="dash-progress-bar">
+ <div id="dash-progress" class="dash-progress"></div>
+ <script>
+ let load = getCookie('loadtime');
+ document.startLoad = Date.now();
+ console.log('Last Load = ' + load);
+ let dashmesg = (width, msg) => {
+ if (document.getElementById('dash-progress')) document.getElementById('dash-progress').style.width = width;
+ if (document.getElementById('dash-progress')) document.getElementById('dash-loader-text').innerHTML = msg;
+ };
+ // setTimeout(() => dashmesg("10%", "Loading Dash..."), load / 10);
+ // setTimeout(() => dashmesg("33%", "Preparing dashboards..."), load / 3);
+ // setTimeout(() => dashmesg("50%", "Initializing scripts..."), load / 2);
+ // setTimeout(() => dashmesg("75%", "Fetching documents..."), load / 4 * 3);
+ // setTimeout(() => dashmesg("100%", "Finalising setup..."), load);
+ </script>
+ </div>
+ <div id="dash-loader-text" class="dash-loader-text">Loading Dash...</div>
</div>
</div>
- </div>
- <!-- <script src="https://hypothes.is/embed.js" async></script> -->
- <div id="root" style="position:absolute;width:100%;height:100%;overflow: hidden;"></div>
- <script src="/bundle.js"></script>
-</body>
-
-</html> \ No newline at end of file
+ <!-- <script src="https://hypothes.is/embed.js" async></script> -->
+ <div id="root" style="position: absolute; width: 100%; height: 100%; overflow: hidden"></div>
+ <script src="/bundle.js"></script>
+ </body>
+</html>
diff --git a/package-lock.json b/package-lock.json
index 0199d3d31..fbfdf0136 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -142,20 +142,20 @@
"integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ=="
},
"@babel/core": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.11.tgz",
- "integrity": "sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ==",
+ "version": "7.22.17",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.17.tgz",
+ "integrity": "sha512-2EENLmhpwplDux5PSsZnSbnSkB3tZ6QTksgO25xwEL7pIDcNOMhF5v/s6RzwjMZzZzw9Ofc30gHv5ChCC8pifQ==",
"requires": {
"@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.22.10",
- "@babel/generator": "^7.22.10",
- "@babel/helper-compilation-targets": "^7.22.10",
- "@babel/helper-module-transforms": "^7.22.9",
- "@babel/helpers": "^7.22.11",
- "@babel/parser": "^7.22.11",
- "@babel/template": "^7.22.5",
- "@babel/traverse": "^7.22.11",
- "@babel/types": "^7.22.11",
+ "@babel/code-frame": "^7.22.13",
+ "@babel/generator": "^7.22.15",
+ "@babel/helper-compilation-targets": "^7.22.15",
+ "@babel/helper-module-transforms": "^7.22.17",
+ "@babel/helpers": "^7.22.15",
+ "@babel/parser": "^7.22.16",
+ "@babel/template": "^7.22.15",
+ "@babel/traverse": "^7.22.17",
+ "@babel/types": "^7.22.17",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -164,29 +164,29 @@
},
"dependencies": {
"@babel/code-frame": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz",
- "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==",
+ "version": "7.22.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
+ "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"requires": {
- "@babel/highlight": "^7.22.10",
+ "@babel/highlight": "^7.22.13",
"chalk": "^2.4.2"
}
},
"@babel/generator": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz",
- "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz",
+ "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==",
"requires": {
- "@babel/types": "^7.22.10",
+ "@babel/types": "^7.22.15",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
}
},
"@babel/highlight": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz",
- "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==",
+ "version": "7.22.13",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz",
+ "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==",
"requires": {
"@babel/helper-validator-identifier": "^7.22.5",
"chalk": "^2.4.2",
@@ -194,35 +194,52 @@
}
},
"@babel/parser": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.11.tgz",
- "integrity": "sha512-R5zb8eJIBPJriQtbH/htEQy4k7E2dHWlD2Y2VT07JCzwYZHBxV5ZYtM0UhXSNMT74LyxuM+b1jdL7pSesXbC/g=="
+ "version": "7.22.16",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz",
+ "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA=="
+ },
+ "@babel/template": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
+ "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
+ "requires": {
+ "@babel/code-frame": "^7.22.13",
+ "@babel/parser": "^7.22.15",
+ "@babel/types": "^7.22.15"
+ }
},
"@babel/traverse": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.11.tgz",
- "integrity": "sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==",
+ "version": "7.22.17",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.17.tgz",
+ "integrity": "sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg==",
"requires": {
- "@babel/code-frame": "^7.22.10",
- "@babel/generator": "^7.22.10",
+ "@babel/code-frame": "^7.22.13",
+ "@babel/generator": "^7.22.15",
"@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-function-name": "^7.22.5",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
- "@babel/parser": "^7.22.11",
- "@babel/types": "^7.22.11",
+ "@babel/parser": "^7.22.16",
+ "@babel/types": "^7.22.17",
"debug": "^4.1.0",
"globals": "^11.1.0"
}
},
"@babel/types": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz",
- "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==",
+ "version": "7.22.17",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz",
+ "integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==",
"requires": {
"@babel/helper-string-parser": "^7.22.5",
- "@babel/helper-validator-identifier": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.15",
"to-fast-properties": "^2.0.0"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz",
+ "integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ=="
+ }
}
},
"debug": {
@@ -270,32 +287,37 @@
}
},
"@babel/helper-builder-binary-assignment-operator-visitor": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.10.tgz",
- "integrity": "sha512-Av0qubwDQxC56DoUReVDeLfMEjYYSN1nZrTUrWkXd7hpU73ymRANkbuDm3yni9npkn+RXy9nNbEJZEzXr7xrfQ==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz",
+ "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==",
"requires": {
- "@babel/types": "^7.22.10"
+ "@babel/types": "^7.22.15"
},
"dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz",
+ "integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ=="
+ },
"@babel/types": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz",
- "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==",
+ "version": "7.22.17",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz",
+ "integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==",
"requires": {
"@babel/helper-string-parser": "^7.22.5",
- "@babel/helper-validator-identifier": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.15",
"to-fast-properties": "^2.0.0"
}
}
}
},
"@babel/helper-compilation-targets": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz",
- "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz",
+ "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==",
"requires": {
"@babel/compat-data": "^7.22.9",
- "@babel/helper-validator-option": "^7.22.5",
+ "@babel/helper-validator-option": "^7.22.15",
"browserslist": "^4.21.9",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
@@ -322,14 +344,14 @@
}
},
"@babel/helper-create-class-features-plugin": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.11.tgz",
- "integrity": "sha512-y1grdYL4WzmUDBRGK0pDbIoFd7UZKoDurDzWEoNMYoj1EL+foGRQNyPWDcC+YyegN5y1DUsFFmzjGijB3nSVAQ==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz",
+ "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==",
"requires": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-function-name": "^7.22.5",
- "@babel/helper-member-expression-to-functions": "^7.22.5",
+ "@babel/helper-member-expression-to-functions": "^7.22.15",
"@babel/helper-optimise-call-expression": "^7.22.5",
"@babel/helper-replace-supers": "^7.22.9",
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
@@ -353,9 +375,9 @@
}
},
"@babel/helper-create-regexp-features-plugin": {
- "version": "7.22.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz",
- "integrity": "sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz",
+ "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==",
"requires": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"regexpu-core": "^5.3.1",
@@ -437,11 +459,28 @@
}
},
"@babel/helper-member-expression-to-functions": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz",
- "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz",
+ "integrity": "sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA==",
"requires": {
- "@babel/types": "^7.22.5"
+ "@babel/types": "^7.22.15"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz",
+ "integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ=="
+ },
+ "@babel/types": {
+ "version": "7.22.17",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz",
+ "integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==",
+ "requires": {
+ "@babel/helper-string-parser": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.15",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/helper-module-imports": {
@@ -453,15 +492,40 @@
}
},
"@babel/helper-module-transforms": {
- "version": "7.22.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz",
- "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==",
+ "version": "7.22.17",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.17.tgz",
+ "integrity": "sha512-XouDDhQESrLHTpnBtCKExJdyY4gJCdrvH2Pyv8r8kovX2U8G0dRUOT45T9XlbLtuu9CLXP15eusnkprhoPV5iQ==",
"requires": {
"@babel/helper-environment-visitor": "^7.22.5",
- "@babel/helper-module-imports": "^7.22.5",
+ "@babel/helper-module-imports": "^7.22.15",
"@babel/helper-simple-access": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
- "@babel/helper-validator-identifier": "^7.22.5"
+ "@babel/helper-validator-identifier": "^7.22.15"
+ },
+ "dependencies": {
+ "@babel/helper-module-imports": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
+ "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
+ "requires": {
+ "@babel/types": "^7.22.15"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz",
+ "integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ=="
+ },
+ "@babel/types": {
+ "version": "7.22.17",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz",
+ "integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==",
+ "requires": {
+ "@babel/helper-string-parser": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.15",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/helper-optimise-call-expression": {
@@ -478,13 +542,13 @@
"integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ=="
},
"@babel/helper-remap-async-to-generator": {
- "version": "7.22.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz",
- "integrity": "sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==",
+ "version": "7.22.17",
+ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.17.tgz",
+ "integrity": "sha512-bxH77R5gjH3Nkde6/LuncQoLaP16THYPscurp1S8z7S9ZgezCyV3G8Hc+TZiCmY8pz4fp8CvKSgtJMW0FkLAxA==",
"requires": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-environment-visitor": "^7.22.5",
- "@babel/helper-wrap-function": "^7.22.9"
+ "@babel/helper-wrap-function": "^7.22.17"
},
"dependencies": {
"@babel/helper-annotate-as-pure": {
@@ -542,66 +606,107 @@
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ=="
},
"@babel/helper-validator-option": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz",
- "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw=="
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz",
+ "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA=="
},
"@babel/helper-wrap-function": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.10.tgz",
- "integrity": "sha512-OnMhjWjuGYtdoO3FmsEFWvBStBAe2QOgwOLsLNDjN+aaiMD8InJk1/O3HSD8lkqTjCgg5YI34Tz15KNNA3p+nQ==",
+ "version": "7.22.17",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.17.tgz",
+ "integrity": "sha512-nAhoheCMlrqU41tAojw9GpVEKDlTS8r3lzFmF0lP52LwblCPbuFSO7nGIZoIcoU5NIm1ABrna0cJExE4Ay6l2Q==",
"requires": {
"@babel/helper-function-name": "^7.22.5",
- "@babel/template": "^7.22.5",
- "@babel/types": "^7.22.10"
+ "@babel/template": "^7.22.15",
+ "@babel/types": "^7.22.17"
},
"dependencies": {
+ "@babel/code-frame": {
+ "version": "7.22.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
+ "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
+ "requires": {
+ "@babel/highlight": "^7.22.13",
+ "chalk": "^2.4.2"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.22.13",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz",
+ "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==",
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.22.16",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz",
+ "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA=="
+ },
+ "@babel/template": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
+ "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
+ "requires": {
+ "@babel/code-frame": "^7.22.13",
+ "@babel/parser": "^7.22.15",
+ "@babel/types": "^7.22.15"
+ }
+ },
"@babel/types": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz",
- "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==",
+ "version": "7.22.17",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz",
+ "integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==",
"requires": {
"@babel/helper-string-parser": "^7.22.5",
- "@babel/helper-validator-identifier": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.15",
"to-fast-properties": "^2.0.0"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz",
+ "integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ=="
+ }
}
}
}
},
"@babel/helpers": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.11.tgz",
- "integrity": "sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz",
+ "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==",
"requires": {
- "@babel/template": "^7.22.5",
- "@babel/traverse": "^7.22.11",
- "@babel/types": "^7.22.11"
+ "@babel/template": "^7.22.15",
+ "@babel/traverse": "^7.22.15",
+ "@babel/types": "^7.22.15"
},
"dependencies": {
"@babel/code-frame": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz",
- "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==",
+ "version": "7.22.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
+ "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"requires": {
- "@babel/highlight": "^7.22.10",
+ "@babel/highlight": "^7.22.13",
"chalk": "^2.4.2"
}
},
"@babel/generator": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz",
- "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz",
+ "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==",
"requires": {
- "@babel/types": "^7.22.10",
+ "@babel/types": "^7.22.15",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
}
},
"@babel/highlight": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz",
- "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==",
+ "version": "7.22.13",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz",
+ "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==",
"requires": {
"@babel/helper-validator-identifier": "^7.22.5",
"chalk": "^2.4.2",
@@ -609,35 +714,52 @@
}
},
"@babel/parser": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.11.tgz",
- "integrity": "sha512-R5zb8eJIBPJriQtbH/htEQy4k7E2dHWlD2Y2VT07JCzwYZHBxV5ZYtM0UhXSNMT74LyxuM+b1jdL7pSesXbC/g=="
+ "version": "7.22.16",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz",
+ "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA=="
+ },
+ "@babel/template": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
+ "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
+ "requires": {
+ "@babel/code-frame": "^7.22.13",
+ "@babel/parser": "^7.22.15",
+ "@babel/types": "^7.22.15"
+ }
},
"@babel/traverse": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.11.tgz",
- "integrity": "sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==",
+ "version": "7.22.17",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.17.tgz",
+ "integrity": "sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg==",
"requires": {
- "@babel/code-frame": "^7.22.10",
- "@babel/generator": "^7.22.10",
+ "@babel/code-frame": "^7.22.13",
+ "@babel/generator": "^7.22.15",
"@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-function-name": "^7.22.5",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
- "@babel/parser": "^7.22.11",
- "@babel/types": "^7.22.11",
+ "@babel/parser": "^7.22.16",
+ "@babel/types": "^7.22.17",
"debug": "^4.1.0",
"globals": "^11.1.0"
}
},
"@babel/types": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz",
- "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==",
+ "version": "7.22.17",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz",
+ "integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==",
"requires": {
"@babel/helper-string-parser": "^7.22.5",
- "@babel/helper-validator-identifier": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.15",
"to-fast-properties": "^2.0.0"
+ },
+ "dependencies": {
+ "@babel/helper-validator-identifier": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz",
+ "integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ=="
+ }
}
},
"debug": {
@@ -671,9 +793,9 @@
"integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q=="
},
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz",
- "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz",
+ "integrity": "sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==",
"requires": {
"@babel/helper-plugin-utils": "^7.22.5"
},
@@ -686,13 +808,13 @@
}
},
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz",
- "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz",
+ "integrity": "sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==",
"requires": {
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
- "@babel/plugin-transform-optional-chaining": "^7.22.5"
+ "@babel/plugin-transform-optional-chaining": "^7.22.15"
},
"dependencies": {
"@babel/helper-plugin-utils": {
@@ -941,9 +1063,9 @@
}
},
"@babel/plugin-transform-async-generator-functions": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.11.tgz",
- "integrity": "sha512-0pAlmeRJn6wU84zzZsEOx1JV1Jf8fqO9ok7wofIJwUnplYo247dcd24P+cMJht7ts9xkzdtB0EPHmOb7F+KzXw==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.15.tgz",
+ "integrity": "sha512-jBm1Es25Y+tVoTi5rfd5t1KLmL8ogLKpXszboWOTTtGFGz2RKnQe2yn7HbZ+kb/B8N0FVSGQo874NSlOU1T4+w==",
"requires": {
"@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5",
@@ -991,9 +1113,9 @@
}
},
"@babel/plugin-transform-block-scoping": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.10.tgz",
- "integrity": "sha512-1+kVpGAOOI1Albt6Vse7c8pHzcZQdQKW+wJH+g8mCaszOdDVwRXa/slHPqIw+oJAJANTKDMuM2cBdV0Dg618Vg==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.15.tgz",
+ "integrity": "sha512-G1czpdJBZCtngoK1sJgloLiOHUnkb/bLZwqVZD8kXmq0ZnVfTTWUcs9OWtp0mBtYJ+4LQY1fllqBkOIPhXmFmw==",
"requires": {
"@babel/helper-plugin-utils": "^7.22.5"
},
@@ -1039,17 +1161,17 @@
}
},
"@babel/plugin-transform-classes": {
- "version": "7.22.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz",
- "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz",
+ "integrity": "sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==",
"requires": {
"@babel/helper-annotate-as-pure": "^7.22.5",
- "@babel/helper-compilation-targets": "^7.22.6",
+ "@babel/helper-compilation-targets": "^7.22.15",
"@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-function-name": "^7.22.5",
"@babel/helper-optimise-call-expression": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5",
- "@babel/helper-replace-supers": "^7.22.5",
+ "@babel/helper-replace-supers": "^7.22.9",
"@babel/helper-split-export-declaration": "^7.22.6",
"globals": "^11.1.0"
},
@@ -1086,9 +1208,9 @@
}
},
"@babel/plugin-transform-destructuring": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.10.tgz",
- "integrity": "sha512-dPJrL0VOyxqLM9sritNbMSGx/teueHF/htMKrPT7DNxccXxRDPYqlgPFFdr8u+F+qUZOkZoXue/6rL5O5GduEw==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.15.tgz",
+ "integrity": "sha512-HzG8sFl1ZVGTme74Nw+X01XsUTqERVQ6/RLHo3XjGRzm7XD6QTtfS3NJotVgCGy8BzkDqRjRBD8dAyJn5TuvSQ==",
"requires": {
"@babel/helper-plugin-utils": "^7.22.5"
},
@@ -1180,9 +1302,9 @@
}
},
"@babel/plugin-transform-for-of": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz",
- "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz",
+ "integrity": "sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==",
"requires": {
"@babel/helper-plugin-utils": "^7.22.5"
},
@@ -1290,11 +1412,11 @@
}
},
"@babel/plugin-transform-modules-commonjs": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.11.tgz",
- "integrity": "sha512-o2+bg7GDS60cJMgz9jWqRUsWkMzLCxp+jFDeDUT5sjRlAxcJWZ2ylNdI7QQ2+CH5hWu7OnN+Cv3htt7AkSf96g==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.15.tgz",
+ "integrity": "sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg==",
"requires": {
- "@babel/helper-module-transforms": "^7.22.9",
+ "@babel/helper-module-transforms": "^7.22.15",
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/helper-simple-access": "^7.22.5"
},
@@ -1404,15 +1526,15 @@
}
},
"@babel/plugin-transform-object-rest-spread": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.11.tgz",
- "integrity": "sha512-nX8cPFa6+UmbepISvlf5jhQyaC7ASs/7UxHmMkuJ/k5xSHvDPPaibMo+v3TXwU/Pjqhep/nFNpd3zn4YR59pnw==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz",
+ "integrity": "sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==",
"requires": {
"@babel/compat-data": "^7.22.9",
- "@babel/helper-compilation-targets": "^7.22.10",
+ "@babel/helper-compilation-targets": "^7.22.15",
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
- "@babel/plugin-transform-parameters": "^7.22.5"
+ "@babel/plugin-transform-parameters": "^7.22.15"
},
"dependencies": {
"@babel/helper-plugin-utils": {
@@ -1455,9 +1577,9 @@
}
},
"@babel/plugin-transform-optional-chaining": {
- "version": "7.22.12",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.12.tgz",
- "integrity": "sha512-7XXCVqZtyFWqjDsYDY4T45w4mlx1rf7aOgkc/Ww76xkgBiOlmjPkx36PBLHa1k1rwWvVgYMPsbuVnIamx2ZQJw==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.15.tgz",
+ "integrity": "sha512-ngQ2tBhq5vvSJw2Q2Z9i7ealNkpDMU0rGWnHPKqRZO0tzZ5tlaoz4hDvhXioOoaE0X2vfNss1djwg0DXlfu30A==",
"requires": {
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
@@ -1472,9 +1594,9 @@
}
},
"@babel/plugin-transform-parameters": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz",
- "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz",
+ "integrity": "sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==",
"requires": {
"@babel/helper-plugin-utils": "^7.22.5"
},
@@ -1559,15 +1681,15 @@
}
},
"@babel/plugin-transform-react-jsx": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz",
- "integrity": "sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz",
+ "integrity": "sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA==",
"requires": {
"@babel/helper-annotate-as-pure": "^7.22.5",
- "@babel/helper-module-imports": "^7.22.5",
+ "@babel/helper-module-imports": "^7.22.15",
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-jsx": "^7.22.5",
- "@babel/types": "^7.22.5"
+ "@babel/types": "^7.22.15"
},
"dependencies": {
"@babel/helper-annotate-as-pure": {
@@ -1578,11 +1700,24 @@
"@babel/types": "^7.22.5"
}
},
+ "@babel/helper-module-imports": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
+ "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
+ "requires": {
+ "@babel/types": "^7.22.15"
+ }
+ },
"@babel/helper-plugin-utils": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
"integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
},
+ "@babel/helper-validator-identifier": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz",
+ "integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ=="
+ },
"@babel/plugin-syntax-jsx": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz",
@@ -1590,6 +1725,16 @@
"requires": {
"@babel/helper-plugin-utils": "^7.22.5"
}
+ },
+ "@babel/types": {
+ "version": "7.22.17",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz",
+ "integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==",
+ "requires": {
+ "@babel/helper-string-parser": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.15",
+ "to-fast-properties": "^2.0.0"
+ }
}
}
},
@@ -1733,12 +1878,12 @@
}
},
"@babel/plugin-transform-typescript": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.11.tgz",
- "integrity": "sha512-0E4/L+7gfvHub7wsbTv03oRtD69X31LByy44fGmFzbZScpupFByMcgCJ0VbBTkzyjSJKuRoGN8tcijOWKTmqOA==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.15.tgz",
+ "integrity": "sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA==",
"requires": {
"@babel/helper-annotate-as-pure": "^7.22.5",
- "@babel/helper-create-class-features-plugin": "^7.22.11",
+ "@babel/helper-create-class-features-plugin": "^7.22.15",
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-typescript": "^7.22.5"
},
@@ -1822,16 +1967,16 @@
}
},
"@babel/preset-env": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.10.tgz",
- "integrity": "sha512-riHpLb1drNkpLlocmSyEg4oYJIQFeXAK/d7rI6mbD0XsvoTOOweXDmQPG/ErxsEhWk3rl3Q/3F6RFQlVFS8m0A==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.15.tgz",
+ "integrity": "sha512-tZFHr54GBkHk6hQuVA8w4Fmq+MSPsfvMG0vPnOYyTnJpyfMqybL8/MbNCPRT9zc2KBO2pe4tq15g6Uno4Jpoag==",
"requires": {
"@babel/compat-data": "^7.22.9",
- "@babel/helper-compilation-targets": "^7.22.10",
+ "@babel/helper-compilation-targets": "^7.22.15",
"@babel/helper-plugin-utils": "^7.22.5",
- "@babel/helper-validator-option": "^7.22.5",
- "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5",
- "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5",
+ "@babel/helper-validator-option": "^7.22.15",
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.15",
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.15",
"@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
"@babel/plugin-syntax-async-generators": "^7.8.4",
"@babel/plugin-syntax-class-properties": "^7.12.13",
@@ -1852,41 +1997,41 @@
"@babel/plugin-syntax-top-level-await": "^7.14.5",
"@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
"@babel/plugin-transform-arrow-functions": "^7.22.5",
- "@babel/plugin-transform-async-generator-functions": "^7.22.10",
+ "@babel/plugin-transform-async-generator-functions": "^7.22.15",
"@babel/plugin-transform-async-to-generator": "^7.22.5",
"@babel/plugin-transform-block-scoped-functions": "^7.22.5",
- "@babel/plugin-transform-block-scoping": "^7.22.10",
+ "@babel/plugin-transform-block-scoping": "^7.22.15",
"@babel/plugin-transform-class-properties": "^7.22.5",
- "@babel/plugin-transform-class-static-block": "^7.22.5",
- "@babel/plugin-transform-classes": "^7.22.6",
+ "@babel/plugin-transform-class-static-block": "^7.22.11",
+ "@babel/plugin-transform-classes": "^7.22.15",
"@babel/plugin-transform-computed-properties": "^7.22.5",
- "@babel/plugin-transform-destructuring": "^7.22.10",
+ "@babel/plugin-transform-destructuring": "^7.22.15",
"@babel/plugin-transform-dotall-regex": "^7.22.5",
"@babel/plugin-transform-duplicate-keys": "^7.22.5",
- "@babel/plugin-transform-dynamic-import": "^7.22.5",
+ "@babel/plugin-transform-dynamic-import": "^7.22.11",
"@babel/plugin-transform-exponentiation-operator": "^7.22.5",
- "@babel/plugin-transform-export-namespace-from": "^7.22.5",
- "@babel/plugin-transform-for-of": "^7.22.5",
+ "@babel/plugin-transform-export-namespace-from": "^7.22.11",
+ "@babel/plugin-transform-for-of": "^7.22.15",
"@babel/plugin-transform-function-name": "^7.22.5",
- "@babel/plugin-transform-json-strings": "^7.22.5",
+ "@babel/plugin-transform-json-strings": "^7.22.11",
"@babel/plugin-transform-literals": "^7.22.5",
- "@babel/plugin-transform-logical-assignment-operators": "^7.22.5",
+ "@babel/plugin-transform-logical-assignment-operators": "^7.22.11",
"@babel/plugin-transform-member-expression-literals": "^7.22.5",
"@babel/plugin-transform-modules-amd": "^7.22.5",
- "@babel/plugin-transform-modules-commonjs": "^7.22.5",
- "@babel/plugin-transform-modules-systemjs": "^7.22.5",
+ "@babel/plugin-transform-modules-commonjs": "^7.22.15",
+ "@babel/plugin-transform-modules-systemjs": "^7.22.11",
"@babel/plugin-transform-modules-umd": "^7.22.5",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5",
"@babel/plugin-transform-new-target": "^7.22.5",
- "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5",
- "@babel/plugin-transform-numeric-separator": "^7.22.5",
- "@babel/plugin-transform-object-rest-spread": "^7.22.5",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11",
+ "@babel/plugin-transform-numeric-separator": "^7.22.11",
+ "@babel/plugin-transform-object-rest-spread": "^7.22.15",
"@babel/plugin-transform-object-super": "^7.22.5",
- "@babel/plugin-transform-optional-catch-binding": "^7.22.5",
- "@babel/plugin-transform-optional-chaining": "^7.22.10",
- "@babel/plugin-transform-parameters": "^7.22.5",
+ "@babel/plugin-transform-optional-catch-binding": "^7.22.11",
+ "@babel/plugin-transform-optional-chaining": "^7.22.15",
+ "@babel/plugin-transform-parameters": "^7.22.15",
"@babel/plugin-transform-private-methods": "^7.22.5",
- "@babel/plugin-transform-private-property-in-object": "^7.22.5",
+ "@babel/plugin-transform-private-property-in-object": "^7.22.11",
"@babel/plugin-transform-property-literals": "^7.22.5",
"@babel/plugin-transform-regenerator": "^7.22.10",
"@babel/plugin-transform-reserved-words": "^7.22.5",
@@ -1900,7 +2045,7 @@
"@babel/plugin-transform-unicode-regex": "^7.22.5",
"@babel/plugin-transform-unicode-sets-regex": "^7.22.5",
"@babel/preset-modules": "0.1.6-no-external-plugins",
- "@babel/types": "^7.22.10",
+ "@babel/types": "^7.22.15",
"babel-plugin-polyfill-corejs2": "^0.4.5",
"babel-plugin-polyfill-corejs3": "^0.8.3",
"babel-plugin-polyfill-regenerator": "^0.5.2",
@@ -1913,13 +2058,18 @@
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
"integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
},
+ "@babel/helper-validator-identifier": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz",
+ "integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ=="
+ },
"@babel/types": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz",
- "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==",
+ "version": "7.22.17",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz",
+ "integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==",
"requires": {
"@babel/helper-string-parser": "^7.22.5",
- "@babel/helper-validator-identifier": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.15",
"to-fast-properties": "^2.0.0"
}
},
@@ -1941,14 +2091,14 @@
}
},
"@babel/preset-react": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.5.tgz",
- "integrity": "sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.15.tgz",
+ "integrity": "sha512-Csy1IJ2uEh/PecCBXXoZGAZBeCATTuePzCSB7dLYWS0vOEj6CNpjxIhW4duWwZodBNueH7QO14WbGn8YyeuN9w==",
"requires": {
"@babel/helper-plugin-utils": "^7.22.5",
- "@babel/helper-validator-option": "^7.22.5",
+ "@babel/helper-validator-option": "^7.22.15",
"@babel/plugin-transform-react-display-name": "^7.22.5",
- "@babel/plugin-transform-react-jsx": "^7.22.5",
+ "@babel/plugin-transform-react-jsx": "^7.22.15",
"@babel/plugin-transform-react-jsx-development": "^7.22.5",
"@babel/plugin-transform-react-pure-annotations": "^7.22.5"
},
@@ -1961,15 +2111,15 @@
}
},
"@babel/preset-typescript": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.11.tgz",
- "integrity": "sha512-tWY5wyCZYBGY7IlalfKI1rLiGlIfnwsRHZqlky0HVv8qviwQ1Uo/05M6+s+TcTCVa6Bmoo2uJW5TMFX6Wa4qVg==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.15.tgz",
+ "integrity": "sha512-HblhNmh6yM+cU4VwbBRpxFhxsTdfS1zsvH9W+gEjD0ARV9+8B4sNfpI6GuhePti84nuvhiwKS539jKPFHskA9A==",
"requires": {
"@babel/helper-plugin-utils": "^7.22.5",
- "@babel/helper-validator-option": "^7.22.5",
+ "@babel/helper-validator-option": "^7.22.15",
"@babel/plugin-syntax-jsx": "^7.22.5",
- "@babel/plugin-transform-modules-commonjs": "^7.22.11",
- "@babel/plugin-transform-typescript": "^7.22.11"
+ "@babel/plugin-transform-modules-commonjs": "^7.22.15",
+ "@babel/plugin-transform-typescript": "^7.22.15"
},
"dependencies": {
"@babel/helper-plugin-utils": {
@@ -2702,26 +2852,26 @@
}
},
"@floating-ui/dom": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.1.tgz",
- "integrity": "sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==",
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.2.tgz",
+ "integrity": "sha512-6ArmenS6qJEWmwzczWyhvrXRdI/rI78poBcW0h/456+onlabit+2G+QxHx5xTOX60NBJQXjsCLFbW2CmsXpUog==",
"requires": {
"@floating-ui/core": "^1.4.1",
"@floating-ui/utils": "^0.1.1"
}
},
"@floating-ui/react-dom": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz",
- "integrity": "sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz",
+ "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==",
"requires": {
- "@floating-ui/dom": "^1.3.0"
+ "@floating-ui/dom": "^1.5.1"
}
},
"@floating-ui/utils": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.1.tgz",
- "integrity": "sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw=="
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.2.tgz",
+ "integrity": "sha512-ou3elfqG/hZsbmF4bxeJhPHIf3G2pm0ujc39hYEZrfVqt7Vk/Zji6CXc3W0pmYM8BW1g40U+akTl9DKZhFhInQ=="
},
"@fortawesome/fontawesome-common-types": {
"version": "6.4.0",
@@ -3232,18 +3382,18 @@
}
},
"@mui/styled-engine-sc": {
- "version": "5.14.6",
- "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-5.14.6.tgz",
- "integrity": "sha512-7/KXXdDLjpQAmbmIhUs1x7nzqooEiHkidQOXCIH04NiVa4KRxP4v/bOWV/5GpgZi1Aky5ruf9IVyH3jxYIW3JA==",
+ "version": "5.14.8",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-5.14.8.tgz",
+ "integrity": "sha512-s4bu3oqE2s3eeB6v/03NRB0N0yswA74Azn6N53PPq87dsvGOyvKLj707bJe+9HfbcbkdksQXP+2NUDmtmUDcig==",
"requires": {
"@babel/runtime": "^7.22.10",
"prop-types": "^15.8.1"
},
"dependencies": {
"@babel/runtime": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz",
- "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz",
+ "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==",
"requires": {
"regenerator-runtime": "^0.14.0"
}
@@ -6537,9 +6687,9 @@
}
},
"browndash-components": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.2.tgz",
- "integrity": "sha512-5ONrqd6qYYMjWuayhhdwU4tlrtr+7c0n9MAMXg4jccdd9OxnmdlWpjPzOBYl3slE+mSpKcycwKarwYND+eM0NA==",
+ "version": "0.1.36",
+ "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.36.tgz",
+ "integrity": "sha512-++Xxn67r9ETz7pQr9dE09dMgPJ6rltEzwYWifpI98AQct4RmEBfiPp5eLG94+143XllxZy++KBf9YJ8AYpTEkQ==",
"requires": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
@@ -6554,9 +6704,9 @@
},
"dependencies": {
"@babel/cli": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.22.10.tgz",
- "integrity": "sha512-rM9ZMmaII630zGvtMtQ3P4GyHs28CHLYE9apLG7L8TgaSqcfoIGrlLSLsh4Q8kDTdZQQEXZm1M0nQtOvU/2heg==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.22.15.tgz",
+ "integrity": "sha512-prtg5f6zCERIaECeTZzd2fMtVjlfjhUcO+fBLQ6DXXdq5FljN+excVitJ2nogsusdf31LeqkjAfXZ7Xq+HmN8g==",
"requires": {
"@jridgewell/trace-mapping": "^0.3.17",
"@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3",
@@ -6657,15 +6807,15 @@
"integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
},
"@mui/base": {
- "version": "5.0.0-beta.12",
- "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.12.tgz",
- "integrity": "sha512-tZjjXNAyUpwSDT1uRliZMhRQkWYzELJ8Qi61EuOMRpi36HIwnK2T7Nr4RI423Sv8G2EEikDAZj7je33eNd73NQ==",
+ "version": "5.0.0-beta.14",
+ "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.14.tgz",
+ "integrity": "sha512-Je/9JzzYObsuLCIClgE8XvXNFb55IEz8n2NtStUfASfNiVrwiR8t6VVFFuhofehkyTIN34tq1qbBaOjCnOovBw==",
"requires": {
"@babel/runtime": "^7.22.10",
"@emotion/is-prop-valid": "^1.2.1",
"@floating-ui/react-dom": "^2.0.1",
"@mui/types": "^7.2.4",
- "@mui/utils": "^5.14.6",
+ "@mui/utils": "^5.14.8",
"@popperjs/core": "^2.11.8",
"clsx": "^2.0.0",
"prop-types": "^15.8.1",
@@ -6673,9 +6823,9 @@
},
"dependencies": {
"@babel/runtime": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz",
- "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz",
+ "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==",
"requires": {
"regenerator-runtime": "^0.14.0"
}
@@ -6683,21 +6833,21 @@
}
},
"@mui/core-downloads-tracker": {
- "version": "5.14.6",
- "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.6.tgz",
- "integrity": "sha512-QZEU3pyGWLuaHbxvOlShol7U1FVgzWBR0OH9H8D7L8w4/vto5N5jJVvlqFQS3T0zbR6YGHxFaiL6Ky87jQg7aw=="
+ "version": "5.14.8",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.8.tgz",
+ "integrity": "sha512-8V7ZOC/lKkM03TRHqaThQFIq6bWPnj7L/ZWPh0ymldYFFyh8XdF0ywTgafsofDNYT4StlNknbaTjVHBma3SNjQ=="
},
"@mui/material": {
- "version": "5.14.6",
- "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.6.tgz",
- "integrity": "sha512-C3UgGrmtvcGkQkm0ONBU7bTdapTjQc2Se3b2354xMmU7lgSgW7VM6EP9wIH5XqqoJ60m9l/s9kbTWX0Y+EaWvA==",
+ "version": "5.14.8",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.8.tgz",
+ "integrity": "sha512-fqvDGGF1pXwOOL/f0Gw+KHo/67hasRpf2ApTIJkbuONOk9AUb2jnYMEqCWmL2sUcbbE3ShMbHl8N7HPSsRv1/A==",
"requires": {
"@babel/runtime": "^7.22.10",
- "@mui/base": "5.0.0-beta.12",
- "@mui/core-downloads-tracker": "^5.14.6",
- "@mui/system": "^5.14.6",
+ "@mui/base": "5.0.0-beta.14",
+ "@mui/core-downloads-tracker": "^5.14.8",
+ "@mui/system": "^5.14.8",
"@mui/types": "^7.2.4",
- "@mui/utils": "^5.14.6",
+ "@mui/utils": "^5.14.8",
"@types/react-transition-group": "^4.4.6",
"clsx": "^2.0.0",
"csstype": "^3.1.2",
@@ -6707,9 +6857,9 @@
},
"dependencies": {
"@babel/runtime": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz",
- "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz",
+ "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==",
"requires": {
"regenerator-runtime": "^0.14.0"
}
@@ -6717,19 +6867,19 @@
}
},
"@mui/private-theming": {
- "version": "5.14.6",
- "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.6.tgz",
- "integrity": "sha512-3VBLFGizBXfofyk33bwRg6t9L648aKnLmOKPfY1wFuiXq3AEYwobK65iDci/tHKxm/VKbZ6A7PFjLejvB3EvRQ==",
+ "version": "5.14.8",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.8.tgz",
+ "integrity": "sha512-iBzpcl3Mh92XaYpYPdgzzRxNGkjpoDz8rf8/q5m+EBPowFEHV+CCS9hC0Q2pOKLW3VFFikA7w/GHt7n++40JGQ==",
"requires": {
"@babel/runtime": "^7.22.10",
- "@mui/utils": "^5.14.6",
+ "@mui/utils": "^5.14.8",
"prop-types": "^15.8.1"
},
"dependencies": {
"@babel/runtime": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz",
- "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz",
+ "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==",
"requires": {
"regenerator-runtime": "^0.14.0"
}
@@ -6737,9 +6887,9 @@
}
},
"@mui/styled-engine": {
- "version": "5.14.6",
- "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.6.tgz",
- "integrity": "sha512-I6zeu/OP1Hk4NsX1Oj85TiYl1dER0JMsLJVn76J1Ihl24A5EbiZQKJp3Mn+ufA79ypkdAvM9aQCAQyiVBFcUHg==",
+ "version": "5.14.8",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.8.tgz",
+ "integrity": "sha512-LGwOav/Y40PZWZ2yDk4beUoRlc57Vg+Vpxi9V9BBtT2ESAucCgFobkt+T8eVLMWF9huUou5pwKgLSU5pF90hBg==",
"requires": {
"@babel/runtime": "^7.22.10",
"@emotion/cache": "^11.11.0",
@@ -6748,9 +6898,9 @@
},
"dependencies": {
"@babel/runtime": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz",
- "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz",
+ "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==",
"requires": {
"regenerator-runtime": "^0.14.0"
}
@@ -6758,24 +6908,24 @@
}
},
"@mui/system": {
- "version": "5.14.6",
- "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.6.tgz",
- "integrity": "sha512-/n0ae1MegWjiV1BpRU8jgg4E0zBjeB2VYsT/68ag/xaDuq3/TaDKJeT9REIvyBvwlG3CI3S2O+tRELktxCD1kg==",
+ "version": "5.14.8",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.8.tgz",
+ "integrity": "sha512-Dxnasv7Pj5hYe4ZZFKJZu4ufKm6cxpitWt3A+qMPps22YhqyeEqgDBq/HsAB3GOjqDP40fTAvQvS/Hguf4SJuw==",
"requires": {
"@babel/runtime": "^7.22.10",
- "@mui/private-theming": "^5.14.6",
- "@mui/styled-engine": "^5.14.6",
+ "@mui/private-theming": "^5.14.8",
+ "@mui/styled-engine": "^5.14.8",
"@mui/types": "^7.2.4",
- "@mui/utils": "^5.14.6",
+ "@mui/utils": "^5.14.8",
"clsx": "^2.0.0",
"csstype": "^3.1.2",
"prop-types": "^15.8.1"
},
"dependencies": {
"@babel/runtime": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz",
- "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz",
+ "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==",
"requires": {
"regenerator-runtime": "^0.14.0"
}
@@ -6783,9 +6933,9 @@
}
},
"@mui/utils": {
- "version": "5.14.6",
- "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.6.tgz",
- "integrity": "sha512-AznpqLu6hrFnpHgcvsSSMCG+cDbkcCYfo+daUwBVReNYv4l+NQ8+wvBAF4aUMi155N7xWbbgh0cyKs6Wdsm3aA==",
+ "version": "5.14.8",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.8.tgz",
+ "integrity": "sha512-1Ls2FfyY2yVSz9NEqedh3J8JAbbZAnUWkOWLE2f4/Hc4T5UWHMfzBLLrCqExfqyfyU+uXYJPGeNIsky6f8Gh5Q==",
"requires": {
"@babel/runtime": "^7.22.10",
"@types/prop-types": "^15.7.5",
@@ -6795,9 +6945,9 @@
},
"dependencies": {
"@babel/runtime": {
- "version": "7.22.11",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz",
- "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz",
+ "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==",
"requires": {
"regenerator-runtime": "^0.14.0"
}
@@ -7094,12 +7244,97 @@
"strip-ansi": "^7.0.1"
}
},
+ "string-width-cjs": {
+ "version": "npm:string-width@4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ }
+ }
+ },
"strip-ansi": {
"version": "7.1.0",
"bundled": true,
"requires": {
"ansi-regex": "^6.0.1"
}
+ },
+ "strip-ansi-cjs": {
+ "version": "npm:strip-ansi@6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+ }
+ }
+ },
+ "wrap-ansi-cjs": {
+ "version": "npm:wrap-ansi@7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ }
+ }
}
}
},
@@ -8604,7 +8839,7 @@
}
},
"string-width-cjs": {
- "version": "npm:string-width@4.2.3",
+ "version": "npm:string-width-cjs@4.2.3",
"bundled": true,
"requires": {
"emoji-regex": "^8.0.0",
@@ -8627,7 +8862,7 @@
}
},
"strip-ansi-cjs": {
- "version": "npm:strip-ansi@6.0.1",
+ "version": "npm:strip-ansi-cjs@6.0.1",
"bundled": true,
"requires": {
"ansi-regex": "^5.0.1"
@@ -8786,7 +9021,7 @@
}
},
"wrap-ansi-cjs": {
- "version": "npm:wrap-ansi@7.0.0",
+ "version": "npm:wrap-ansi-cjs@7.0.0",
"bundled": true,
"requires": {
"ansi-styles": "^4.0.0",
@@ -8818,9 +9053,9 @@
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="
},
"postcss": {
- "version": "8.4.28",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz",
- "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==",
+ "version": "8.4.29",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz",
+ "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==",
"requires": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
@@ -9977,9 +10212,9 @@
"integrity": "sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw=="
},
"core-js-compat": {
- "version": "3.32.1",
- "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.1.tgz",
- "integrity": "sha512-GSvKDv4wE0bPnQtjklV101juQ85g6H3rm5PDP20mqlS5j0kXF3pP97YvAu5hl+uFHqMictp3b2VxOHljWMAtuA==",
+ "version": "3.32.2",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.2.tgz",
+ "integrity": "sha512-+GjlguTDINOijtVRUxrQOv3kfu9rl+qPNdX2LTbJ/ZyVTuxK+ksVSAGX1nHstu4hrv1En/uPTtWgq2gI5wt4AQ==",
"requires": {
"browserslist": "^4.21.10"
},
@@ -9996,14 +10231,14 @@
}
},
"caniuse-lite": {
- "version": "1.0.30001524",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz",
- "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA=="
+ "version": "1.0.30001532",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001532.tgz",
+ "integrity": "sha512-FbDFnNat3nMnrROzqrsg314zhqN5LGQ1kyyMk2opcrwGbVGpHRhgCWtAgD5YJUqNAiQ+dklreil/c3Qf1dfCTw=="
},
"electron-to-chromium": {
- "version": "1.4.503",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz",
- "integrity": "sha512-LF2IQit4B0VrUHFeQkWhZm97KuJSGF2WJqq1InpY+ECpFRkXd8yTIaTtJxsO0OKDmiBYwWqcrNaXOurn2T2wiA=="
+ "version": "1.4.515",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.515.tgz",
+ "integrity": "sha512-VTq6vjk3kCfG2qdzQRd/i9dIyVVm0dbtZIgFzrLgfB73mXDQT2HPKVRc1EoZcAVUv9XhXAu08DWqJuababdGGg=="
}
}
},
@@ -14592,6 +14827,11 @@
"node-forge": "^0.10.0"
}
},
+ "google-translate-api-browser": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/google-translate-api-browser/-/google-translate-api-browser-3.0.1.tgz",
+ "integrity": "sha512-KTLodkyGBWMK9IW6QIeJ2zCuju4Z0CLpbkADKo+yLhbSTD4l+CXXpQ/xaynGVAzeBezzJG6qn8MLeqOq3SmW0A=="
+ },
"googleapis": {
"version": "40.0.1",
"resolved": "https://registry.npmjs.org/googleapis/-/googleapis-40.0.1.tgz",
@@ -27177,29 +27417,6 @@
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
},
- "translate-google-api": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/translate-google-api/-/translate-google-api-1.0.4.tgz",
- "integrity": "sha512-KVXmo4+64/H1vIbnzf2zNiJ2JLeEB3jrEnNRP2EFNAGNqna/5bmw/Cps3pCHu0n3BzTOoWh9u6wFvrRYdzQ6Iw==",
- "requires": {
- "axios": "^0.20.0"
- },
- "dependencies": {
- "axios": {
- "version": "0.20.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz",
- "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==",
- "requires": {
- "follow-redirects": "^1.10.0"
- }
- },
- "follow-redirects": {
- "version": "1.15.2",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
- "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
- }
- }
- },
"traverse-chain": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz",
diff --git a/package.json b/package.json
index 918c0633f..d013ad74d 100644
--- a/package.json
+++ b/package.json
@@ -10,11 +10,11 @@
"child_process": "empty"
},
"scripts": {
- "start-release": "cross-env RELEASE=true USE_AZURE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
+ "start-release": "cross-env RELEASE=true USE_AZURE=false NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
"start-release-debug": "cross-env RELEASE=true USE_AZURE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --inspect -- src/server/index.ts",
"start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts",
"oldstart": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug -- src/server/index.ts",
- "debug": "cross-env USE_AZURE=true NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --transpile-only --inspect -- src/server/index.ts",
+ "debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --transpile-only --inspect -- src/server/index.ts",
"monitor": "cross-env MONITORED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node src/server/index.ts",
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production",
"test": "mocha -r ts-node/register test/**/*.ts",
@@ -164,6 +164,7 @@
"@types/three": "^0.126.2",
"@types/web": "0.0.53",
"@webscopeio/react-textarea-autocomplete": "^4.9.1",
+ "D": "^1.0.0",
"adm-zip": "^0.4.16",
"archiver": "^3.1.1",
"array-batcher": "^1.2.3",
@@ -178,7 +179,7 @@
"body-parser": "^1.19.2",
"bootstrap": "^4.6.1",
"brotli": "^1.3.3",
- "browndash-components": "^0.1.02",
+ "browndash-components": "^0.1.36",
"browser-assert": "^1.2.1",
"bson": "^4.6.1",
"canvas": "^2.9.3",
@@ -196,7 +197,6 @@
"cors": "^2.8.5",
"csv-parser": "^3.0.0",
"csv-stringify": "^6.3.0",
- "D": "^1.0.0",
"d3": "^7.6.1",
"depcheck": "^0.9.2",
"equation-editor-react": "github:bobzel/equation-editor-react#useLocally",
@@ -219,6 +219,7 @@
"golden-layout": "^1.5.9",
"google-auth-library": "^4.2.4",
"google-maps-react": "^2.0.6",
+ "google-translate-api-browser": "^3.0.1",
"googleapis": "^40.0.0",
"googlephotos": "^0.2.5",
"got": "^12.0.1",
@@ -327,7 +328,6 @@
"textarea-caret": "^3.1.0",
"three": "^0.127.0",
"tough-cookie": "^4.0.0",
- "translate-google-api": "^1.0.4",
"typescript-collections": "^1.3.3",
"typescript-language-server": "^0.4.0",
"url-loader": "^1.1.2",
diff --git a/src/Utils.ts b/src/Utils.ts
index 29824ba94..b5ca53a33 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -764,11 +764,12 @@ export function lightOrDark(color: any) {
const col = DashColor(nonAlphaColor).rgb();
const colsum = col.red() + col.green() + col.blue();
if (colsum / col.alpha() > 400 || col.alpha() < 0.25) return Colors.DARK_GRAY;
- else return Colors.WHITE;
+ return Colors.WHITE;
}
export function getWordAtPoint(elem: any, x: number, y: number): string | undefined {
if (elem.tagName === 'INPUT') return 'input';
+ if (elem.tagName === 'TEXTAREA') return 'textarea';
if (elem.nodeType === elem.TEXT_NODE) {
const range = elem.ownerDocument.createRange();
range.selectNodeContents(elem);
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 5fdea131b..353e11775 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -32,9 +32,11 @@ export namespace DocServer {
let _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
export function FindDocByTitle(title: string) {
- const foundDocId = Array.from(Object.keys(_cache))
- .filter(key => _cache[key] instanceof Doc)
- .find(key => (_cache[key] as Doc).title === title);
+ const foundDocId =
+ title &&
+ Array.from(Object.keys(_cache))
+ .filter(key => _cache[key] instanceof Doc)
+ .find(key => (_cache[key] as Doc).title === title);
return foundDocId ? (_cache[foundDocId] as Doc) : undefined;
}
@@ -51,6 +53,7 @@ export namespace DocServer {
DocListCast(DocCast(Doc.UserDoc().myLinkDatabase).data).forEach(link => {
if (!references.has(DocCast(link.link_anchor_1)) && !references.has(DocCast(link.link_anchor_2))) {
Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myLinkDatabase), 'data', link);
+ Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
}
});
LinkManager.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
diff --git a/src/client/Network.ts b/src/client/Network.ts
index 39bf69e32..89b31fdca 100644
--- a/src/client/Network.ts
+++ b/src/client/Network.ts
@@ -58,11 +58,16 @@ export namespace Networking {
])
);
}
+ formData.set('fileguids', fileguidpairs.map(pair => pair.guid).join(';'));
+ formData.set('filesize', fileguidpairs.reduce((sum, pair) => sum + pair.file.size, 0).toString());
// If the fileguidpair has a guid to use (From the overwriteDoc) use that guid. Otherwise, generate a new guid.
fileguidpairs.forEach(fileguidpair => formData.append(fileguidpair.guid ?? Utils.GenerateGuid(), fileguidpair.file));
} else {
// Handle the case where fileguidpairs is a single file.
- formData.append(fileguidpairs.guid ?? Utils.GenerateGuid(), fileguidpairs.file);
+ const guids = fileguidpairs.guid ?? Utils.GenerateGuid();
+ formData.set('fileguids', guids);
+ formData.set('filesize', fileguidpairs.file.size.toString());
+ formData.append(guids, fileguidpairs.file);
}
const parameters = {
method: 'POST',
@@ -74,10 +79,10 @@ export namespace Networking {
return response.json();
}
- export async function UploadYoutubeToServer<T extends Upload.FileInformation = Upload.FileInformation>(videoId: string): Promise<Upload.FileResponse<T>[]> {
+ export async function UploadYoutubeToServer<T extends Upload.FileInformation = Upload.FileInformation>(videoId: string, overwriteId?: string): Promise<Upload.FileResponse<T>[]> {
const parameters = {
method: 'POST',
- body: JSON.stringify({ videoId }),
+ body: JSON.stringify({ videoId, overwriteId }),
json: true,
};
const response = await fetch('/uploadYoutubeVideo', parameters);
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index f8d129e79..87010f770 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -30,14 +30,13 @@ export enum DocumentType {
// special purpose wrappers that either take no data or are compositions of lower level types
LINK = 'link',
IMPORT = 'import',
- SLIDER = 'slider',
PRES = 'presentation',
PRESELEMENT = 'preselement',
COLOR = 'color',
YOUTUBE = 'youtube',
COMPARISON = 'comparison',
GROUP = 'group',
- PUSHPIN = "pushpin",
+ PUSHPIN = 'pushpin',
SCRIPTDB = 'scriptdb', // database of scripts
GROUPDB = 'groupdb', // database of groups
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 919958b24..b1632dadf 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -15,7 +15,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro
import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField, YoutubeField } from '../../fields/URLField';
import { inheritParentAcls, SharingPermissions } from '../../fields/util';
import { Upload } from '../../server/SharedMediaTypes';
-import { aggregateBounds, OmitKeys, Utils } from '../../Utils';
+import { OmitKeys, Utils } from '../../Utils';
import { YoutubeBox } from '../apis/youtube/YoutubeBox';
import { DocServer } from '../DocServer';
import { Networking } from '../Network';
@@ -32,7 +32,7 @@ import { ContextMenu } from '../views/ContextMenu';
import { ContextMenuProps } from '../views/ContextMenuItem';
import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss';
import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
-import { AudioBox } from '../views/nodes/AudioBox';
+import { AudioBox, media_state } from '../views/nodes/AudioBox';
import { ColorBox } from '../views/nodes/ColorBox';
import { ComparisonBox } from '../views/nodes/ComparisonBox';
import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
@@ -54,7 +54,6 @@ import { PhysicsSimulationBox } from '../views/nodes/PhysicsBox/PhysicsSimulatio
import { RecordingBox } from '../views/nodes/RecordingBox/RecordingBox';
import { ScreenshotBox } from '../views/nodes/ScreenshotBox';
import { ScriptingBox } from '../views/nodes/ScriptingBox';
-import { SliderBox } from '../views/nodes/SliderBox';
import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
import { PresBox } from '../views/nodes/trails/PresBox';
import { PresElementBox } from '../views/nodes/trails/PresElementBox';
@@ -145,7 +144,6 @@ class CTypeInfo extends FInfo {
class DTypeInfo extends FInfo {
fieldType? = 'enumeration';
values? = Array.from(Object.keys(DocumentType));
- readOnly = true;
}
class DateInfo extends FInfo {
fieldType? = 'date';
@@ -269,7 +267,7 @@ export class DocumentOptions {
_label_maxFontSize?: NUMt = new NumInfo('maximum font size for labelBoxes', false);
stroke_width?: NUMt = new NumInfo('width of an ink stroke', false);
icon_label?: STRt = new StrInfo('label to use for a fontIcon doc (otherwise, the title is used)', false);
- mediaState?: STRt = new StrInfo('status of audio/video media document: "pendingRecording", "recording", "paused", "playing"', false);
+ mediaState?: STRt = new StrInfo(`status of audio/video media document: ${media_state.PendingRecording}, ${media_state.Recording}, ${media_state.Paused}, ${media_state.Playing}`, false);
recording?: BOOLt = new BoolInfo('whether WebCam is recording or not');
autoPlayAnchors?: BOOLt = new BoolInfo('whether to play audio/video when an anchor is clicked in a stackedTimeline.');
dontPlayLinkOnSelect?: BOOLt = new BoolInfo('whether an audio/video should start playing when a link is followed to it.');
@@ -313,6 +311,7 @@ export class DocumentOptions {
_isTimelineLabel?: BOOLt = new BoolInfo('is document a timeline label');
_isLightbox?: BOOLt = new BoolInfo('whether a collection acts as a lightbox by opening lightbox links by hiding all other documents in collection besides link target');
+ mapPin?: DOCt = new DocInfo('pin associated with a config anchor', false);
config_latitude?: NUMt = new NumInfo('latitude of a map', false);
config_longitude?: NUMt = new NumInfo('longitude of map', false);
config_map_zoom?: NUMt = new NumInfo('zoom of map', false);
@@ -337,11 +336,13 @@ export class DocumentOptions {
// freeform properties
_freeform_backgroundGrid?: BOOLt = new BoolInfo('whether background grid is shown on freeform collections');
+ _freeform_scale_min?: NUMt = new NumInfo('how far out a view can zoom (used by image/videoBoxes that are clipped');
+ _freeform_scale_max?: NUMt = new NumInfo('how far in a view can zoom (used by sidebar freeform views');
_freeform_scale?: NUMt = new NumInfo('how much a freeform view has been scaled (zoomed)');
_freeform_panX?: NUMt = new NumInfo('horizontal pan location of a freeform view');
_freeform_panY?: NUMt = new NumInfo('vertical pan location of a freeform view');
_freeform_noAutoPan?: BOOLt = new BoolInfo('disables autopanning when this item is dragged');
- _freeform_noZoom?: BOOLt = new BoolInfo('disables zooming');
+ _freeform_noZoom?: BOOLt = new BoolInfo('disables zooming (used by Pile docs)');
//BUTTONS
buttonText?: string;
@@ -425,7 +426,7 @@ export class DocumentOptions {
treeView_FreezeChildren?: STRt = new StrInfo('set (add, remove, add|remove) to disable adding, removing or both from collection');
sidebar_color?: string; // background color of text sidebar
- sidebar_collectionType?: string; // collection type of text sidebar
+ sidebar_type_collection?: string; // collection type of text sidebar
data_dashboards?: List<any>; // list of dashboards used in shareddocs;
text?: string;
@@ -627,13 +628,6 @@ export namespace Docs {
},
],
[
- DocumentType.SLIDER,
- {
- layout: { view: SliderBox, dataField: defaultDataKey },
- options: {},
- },
- ],
- [
DocumentType.PRES,
{
layout: { view: PresBox, dataField: defaultDataKey },
@@ -1040,7 +1034,7 @@ export namespace Docs {
I[Initializing] = true;
I.type = DocumentType.INK;
I.layout = InkingStroke.LayoutString('stroke');
- I.layout_fitWidth = true;
+ I.layout_fitWidth = false;
I.layout_hideDecorationTitle = true; // don't show title when selected
// I.layout_hideOpenButton = true; // don't show open full screen button when selected
I.color = color;
@@ -1084,7 +1078,7 @@ export namespace Docs {
const nwid = options._nativeWidth || undefined;
const nhght = options._nativeHeight || undefined;
if (!nhght && width && height && nwid) options._nativeHeight = (Number(nwid) * Number(height)) / Number(width);
- return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url ? url : 'http://www.bing.com/'), options);
+ return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url ? url : 'https://www.wikipedia.org/'), options);
}
export function HtmlDocument(html: string, options: DocumentOptions = {}) {
@@ -1195,10 +1189,6 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}) });
}
- export function SliderDocument(options?: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.SLIDER), undefined, { ...(options || {}) });
- }
-
export function FontIconDocument(options?: DocumentOptions) {
return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { ...(options || {}) });
}
@@ -1294,20 +1284,6 @@ export namespace DocUtils {
for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragsDocFilter.split(Doc.FilterSep)[0])) {
const facet = filterFacets[facetKey];
- let links = true;
- const linkedTo = filterFacets['-linkedTo'] && Array.from(Object.keys(filterFacets['-linkedTo']))?.[0];
- const linkedToField = filterFacets['-linkedTo']?.[linkedTo];
- const allLinks = linkedTo && linkedToField ? LinkManager.Instance.getAllRelatedLinks(d) : [];
- // prettier-ignore
- if (linkedTo) {
- if (allLinks.some(d => linkedTo === Field.toScriptString(DocCast(DocCast(d.link_anchor_1)?.[linkedToField]))) || //
- allLinks.some(d => linkedTo === Field.toScriptString(DocCast(DocCast(d.link_anchor_2)?.[linkedToField]))))
- {
- links = true;
- }
- else links = false
- }
-
// facets that match some value in the field of the document (e.g. some text field)
const matches = Object.keys(facet).filter(value => value !== 'cookies' && facet[value] === 'match');
@@ -1317,16 +1293,16 @@ export namespace DocUtils {
// metadata facets that exist
const exists = Object.keys(facet).filter(value => facet[value] === 'exists');
- // metadata facets that exist
+ // facets that unset metadata (a hack for making cookies work)
const unsets = Object.keys(facet).filter(value => facet[value] === 'unset');
- // facets that have an x next to them
+ // facets that specify that a field must not match a specific value
const xs = Object.keys(facet).filter(value => facet[value] === 'x');
- if (!linkedTo && !unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true;
+ if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true;
const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value));
const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value));
- const satisfiesExistsFacets = !exists.length ? true : exists.some(value => d[facetKey] !== undefined);
+ const satisfiesExistsFacets = !exists.length ? true : exists.some(value => (facetKey !== '-linkedTo' ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length));
const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined);
const satisfiesMatchFacets = !matches.length
? true
@@ -1342,11 +1318,11 @@ export namespace DocUtils {
});
// if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria
if (parentCollection?.childFilters_boolean === 'OR') {
- if (links && satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
+ if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
}
// if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria
else {
- if (!links || !satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
+ if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
}
}
return parentCollection?.childFilters_boolean === 'OR' ? false : true;
@@ -1712,32 +1688,16 @@ export namespace DocUtils {
}
export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) {
- let w = 0,
- h = 0;
runInAction(() => {
- docList.forEach(d => {
- DocUtils.iconify(d);
- w = Math.max(NumCast(d._width), w);
- h = Math.max(NumCast(d._height), h);
- });
- docList.forEach((d, i) => {
- d.x = Math.cos((Math.PI * 2 * i) / docList.length) * size;
- d.y = Math.sin((Math.PI * 2 * i) / docList.length) * size;
- d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
- });
- const aggBounds = aggregateBounds(
- docList.map(d => ({ x: NumCast(d.x), y: NumCast(d.y), width: NumCast(d._width), height: NumCast(d._height) })),
- 0,
- 0
- );
docList.forEach((d, i) => {
- d.x = NumCast(d.x) - (aggBounds.r + aggBounds.x) / 2;
- d.y = NumCast(d.y) - (aggBounds.b + aggBounds.y) / 2;
+ DocUtils.iconify(d);
+ d.x = Math.cos((Math.PI * 2 * i) / docList.length) * size - size;
+ d.y = Math.sin((Math.PI * 2 * i) / docList.length) * size - size;
d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
});
});
if (create) {
- const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, dragWhenActive: true });
+ const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', _freeform_noZoom: true, x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, dragWhenActive: true, _layout_fitWidth: false });
newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size;
newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size;
newCollection._width = newCollection._height = size * 2;
@@ -1870,7 +1830,7 @@ export namespace DocUtils {
export function uploadYoutubeVideoLoading(videoId: string, options: DocumentOptions, overwriteDoc?: Doc) {
const generatedDocuments: Doc[] = [];
- Networking.UploadYoutubeToServer(videoId).then(upfiles => {
+ Networking.UploadYoutubeToServer(videoId, overwriteDoc?.[Id]).then(upfiles => {
const {
source: { name, type },
result,
diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
index 843b8bb5f..588bf57d1 100644
--- a/src/client/goldenLayout.js
+++ b/src/client/goldenLayout.js
@@ -1606,10 +1606,10 @@
dragProxyHeight: 200
},
labels: {
- close: 'close',
- maximise: 'maximise',
+ close: 'close tab stack',
+ maximise: 'maximize stack',
minimise: 'minimise',
- popout: 'new tab',
+ popout: 'create new collection tab',
popin: 'pop in',
tabDropdown: 'additional tabs'
}
@@ -2922,7 +2922,7 @@
* @type {String}
*/
lm.controls.Tab._template = '<li class="lm_tab"><i class="lm_left"></i>' +
- '<div class="lm_title_wrap"><input class="lm_title"/></div><div class="lm_close_tab"></div>' +
+ '<div class="lm_title_wrap"><input class="lm_title"/></div><div class="lm_close_tab" title="close tab"></div>' +
'<i class="lm_right"></i></li>';
lm.utils.copy(lm.controls.Tab.prototype, {
@@ -3377,6 +3377,7 @@
if (this.isMaximised === true) {
this.layoutManager._$minimiseItem(this);
} else {
+ return;
this.layoutManager._$maximiseItem(this);
}
@@ -5083,10 +5084,10 @@
'column',
'stack',
'component',
- 'close',
- 'maximise',
+ 'close tab stack',
+ 'maximize stack',
'minimise',
- 'new tab'
+ 'create new collection tab'
];
};
diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx
new file mode 100644
index 000000000..a224b84f4
--- /dev/null
+++ b/src/client/util/BranchingTrailManager.tsx
@@ -0,0 +1,137 @@
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { PresBox } from '../views/nodes/trails';
+import { OverlayView } from '../views/OverlayView';
+import { DocumentManager } from './DocumentManager';
+import { Docs } from '../documents/Documents';
+import { nullAudio } from '../../fields/URLField';
+
+@observer
+export class BranchingTrailManager extends React.Component {
+ public static Instance: BranchingTrailManager;
+
+ constructor(props: any) {
+ super(props);
+ if (!BranchingTrailManager.Instance) {
+ BranchingTrailManager.Instance = this;
+ }
+ }
+
+ setupUi = () => {
+ OverlayView.Instance.addWindow(<BranchingTrailManager></BranchingTrailManager>, { x: 100, y: 150, width: 1000, title: 'Branching Trail'});
+ // OverlayView.Instance.forceUpdate();
+ console.log(OverlayView.Instance);
+ // let hi = Docs.Create.TextDocument("beee", {
+ // x: 100,
+ // y: 100,
+ // })
+ // hi.overlayX = 100;
+ // hi.overlayY = 100;
+
+ // Doc.AddToMyOverlay(hi);
+ console.log(DocumentManager._overlayViews);
+ };
+
+
+ // stack of the history
+ @observable private slideHistoryStack: String[] = [];
+ @action setSlideHistoryStack = action((newArr: String[]) => {
+ this.slideHistoryStack = newArr;
+ });
+
+ @observable private containsSet: Set<String> = new Set<String>();
+
+ // prev pres to copmare with
+ @observable private prevPresId: String | null = null;
+ @action setPrevPres = action((newId: String | null) => {
+ this.prevPresId = newId;
+ });
+
+ // docId to Doc map
+ @observable private docIdToDocMap: Map<String, Doc> = new Map<String, Doc>();
+
+ observeDocumentChange = (targetDoc: Doc, pres: PresBox) => {
+ const presId = pres.props.Document[Id];
+ if (this.prevPresId === presId) {
+ return;
+ }
+
+ const targetDocId = targetDoc[Id];
+ this.docIdToDocMap.set(targetDocId, targetDoc);
+
+ if (this.prevPresId === null) {
+ this.setupUi();
+ }
+
+ if (this.prevPresId === null || this.prevPresId !== presId) {
+ Doc.UserDoc().isBranchingMode = true;
+ this.setPrevPres(presId);
+
+ // REVERT THE SET
+ const stringified = [presId, targetDocId].toString();
+ if (this.containsSet.has([presId, targetDocId].toString())) {
+ // remove all the elements after the targetDocId
+ const newStack = this.slideHistoryStack.slice(0, this.slideHistoryStack.indexOf(stringified));
+ const removed = this.slideHistoryStack.slice(this.slideHistoryStack.indexOf(stringified));
+ this.setSlideHistoryStack(newStack);
+
+ removed.forEach(info => this.containsSet.delete(info.toString()));
+ } else {
+ this.setSlideHistoryStack([...this.slideHistoryStack, stringified]);
+ this.containsSet.add(stringified);
+ }
+ }
+ console.log(this.slideHistoryStack.length);
+ if (this.slideHistoryStack.length === 0) {
+ Doc.UserDoc().isBranchingMode = false;
+ }
+ };
+
+ clickHandler = (e: React.PointerEvent<HTMLButtonElement>, targetDocId: string, removeIndex: number) => {
+ const targetDoc = this.docIdToDocMap.get(targetDocId);
+ if (!targetDoc) {
+ return;
+ }
+
+ const newStack = this.slideHistoryStack.slice(0, removeIndex);
+ const removed = this.slideHistoryStack.slice(removeIndex);
+
+ this.setSlideHistoryStack(newStack);
+
+ removed.forEach(info => this.containsSet.delete(info.toString()));
+ DocumentManager.Instance.showDocument(targetDoc, { willZoomCentered: true });
+ if (this.slideHistoryStack.length === 0) {
+ Doc.UserDoc().isBranchingMode = false;
+ }
+ //PresBox.NavigateToTarget(targetDoc, targetDoc);
+ };
+
+ @computed get trailBreadcrumbs() {
+ return (
+ <div style={{ border: '.5rem solid green', padding: '5px', backgroundColor: 'white', minHeight: '50px', minWidth: '1000px' }}>
+ {this.slideHistoryStack.map((info, index) => {
+ const [presId, targetDocId] = info.split(',');
+ const doc = this.docIdToDocMap.get(targetDocId);
+ if (!doc) {
+ return <></>;
+ }
+ return (
+ <span key={targetDocId}>
+ <button key={index} onPointerDown={e => this.clickHandler(e, targetDocId, index)}>
+ {presId.slice(0, 3) + ':' + doc.title}
+ </button>
+ -{'>'}
+ </span>
+ );
+ })}
+ </div>
+ );
+ }
+
+ render() {
+ return <div>{BranchingTrailManager.Instance.trailBreadcrumbs}</div>;
+ }
+}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index d52e389d6..cc8f72ddf 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -8,17 +8,18 @@ import { RichTextField } from "../../fields/RichTextField";
import { listSpec } from "../../fields/Schema";
import { ScriptField } from "../../fields/ScriptField";
import { Cast, DateCast, DocCast, StrCast } from "../../fields/Types";
-import { nullAudio } from "../../fields/URLField";
+import { nullAudio, WebField } from "../../fields/URLField";
import { SetCachedGroups, SharingPermissions } from "../../fields/util";
import { GestureUtils } from "../../pen-gestures/GestureUtils";
-import { addStyleSheetRule, OmitKeys, Utils } from "../../Utils";
+import { OmitKeys, Utils } from "../../Utils";
import { DocServer } from "../DocServer";
import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents";
import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
import { TreeViewType } from "../views/collections/CollectionTreeView";
import { DashboardView } from "../views/DashboardView";
import { Colors } from "../views/global/globalEnums";
-import { OpenWhere } from "../views/nodes/DocumentView";
+import { media_state } from "../views/nodes/AudioBox";
+import { DocumentView, OpenWhere } from "../views/nodes/DocumentView";
import { ButtonType } from "../views/nodes/FontIconBox/FontIconBox";
import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
import { OverlayView } from "../views/OverlayView";
@@ -27,7 +28,7 @@ import { MakeTemplate } from "./DropConverter";
import { FollowLinkScript } from "./LinkFollower";
import { LinkManager } from "./LinkManager";
import { ScriptingGlobals } from "./ScriptingGlobals";
-import { ColorScheme, SettingsManager } from "./SettingsManager";
+import { ColorScheme } from "./SettingsManager";
import { UndoManager } from "./UndoManager";
interface Button {
@@ -51,7 +52,7 @@ interface Button {
// fields that do not correspond to DocumentOption fields
scripts?: { script?: string; onClick?: string; onDoubleClick?: string }
- funcs?: { [key:string]: string };
+ funcs?: { [key:string]: any};
subMenu?: Button[];
}
@@ -299,7 +300,7 @@ export class CurrentUserUtils {
{ toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)},
{ toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay},
{ toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)},
- { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay},
+ { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)},
{ toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript), funcs: { hidden: "IsNoviceMode()"}},
@@ -475,7 +476,6 @@ export class CurrentUserUtils {
static setupDashboards(doc: Doc, field:string) {
var myDashboards = DocCast(doc[field]);
- const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`;
const newDashboard = `createNewDashboard()`;
const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true,
@@ -486,14 +486,14 @@ export class CurrentUserUtils {
const contextMenuScripts = [/*newDashboard*/] as string[];
const contextMenuLabels = [/*"Create New Dashboard"*/] as string[];
const contextMenuIcons = [/*"plus"*/] as string[];
- const childContextMenuScripts = [toggleDarkTheme, `toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)', 'resetDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters
- const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any, undefined as any];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts
- const childContextMenuLabels = ["Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard", "Reset Dashboard"];// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters
- const childContextMenuIcons = ["chalkboard", "tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
+ const childContextMenuScripts = [`toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)', 'resetDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters
+ const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any, '!IsNoviceMode()'];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts
+ const childContextMenuLabels = ["Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard", "Reset Dashboard"];// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters
+ const childContextMenuIcons = ["tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
const reqdOpts:DocumentOptions = {
title: "My Dashboards", childHideLinkButton: true, treeView_FreezeChildren: "remove|add", treeView_HideTitle: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
- dropAction: "same", treeView_Type: TreeViewType.fileSystem, isFolder: true, isSystem: true, treeView_TruncateTitleWidth: 350, ignoreClick: true,
- layout_headerButton: newDashboardButton, childDragAction: "none",
+ dropAction: "inSame", treeView_Type: TreeViewType.fileSystem, isFolder: true, isSystem: true, treeView_TruncateTitleWidth: 350, ignoreClick: true,
+ layout_headerButton: newDashboardButton, childDragAction: "inSame",
_layout_showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true,
contextMenuLabels:new List<string>(contextMenuLabels),
contextMenuIcons:new List<string>(contextMenuIcons),
@@ -591,7 +591,7 @@ export class CurrentUserUtils {
static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
btnType: ButtonType.ToolButton, _forceActive: true, _layout_hideContextMenu: true,
_dropPropertiesToRemove: new List<string>([ "_layout_hideContextMenu"]),
- _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, isSystem: true, ...opts,
+ /*_nativeWidth: 40, _nativeHeight: 40, */ _width: 40, _height: 40, isSystem: true, ...opts,
})
/// initializes the required buttons in the expanding button menu at the bottom of the Dash window
@@ -607,6 +607,7 @@ export class CurrentUserUtils {
{ scripts: { }, opts: { title: "undoStack", layout: "<UndoStack>", toolTip: "Undo/Redo Stack"}}, // note: layout fields are hacks -- they don't actually run through the JSX parser (yet)
{ scripts: { }, opts: { title: "linker", layout: "<LinkingUI>", toolTip: "link started"}},
{ scripts: { }, opts: { title: "currently playing", layout: "<CurrentlyPlayingUI>", toolTip: "currently playing media"}},
+ { scripts: { }, opts: { title: "Branching", layout: "<Branching>", toolTip: "Branch, baby!"}}
];
const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts));
const dockBtnsReqdOpts:DocumentOptions = {
@@ -625,6 +626,11 @@ export class CurrentUserUtils {
{ title: "Z order", icon: "z", toolTip: "Keep Z order on Drag", btnType: ButtonType.ToggleButton, expertMode: false, funcs: {}, scripts: { onClick: '{ return toggleRaiseOnDrag(_readOnly_);}'}}, // Only when floating document is selected in freeform
]
}
+ static stackTools(): Button[] {
+ return [
+ { title: "Center", icon: "align-center", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"center", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ ]
+ }
static viewTools(): Button[] {
return [
{ title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
@@ -668,7 +674,7 @@ export class CurrentUserUtils {
return [
{ title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: "pen", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }},
{ title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: "write", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}'} },
- { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", toolType: "eraser", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }},
+ { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", toolType: "eraser", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
{ title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType:GestureUtils.Gestures.Circle, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
{ title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType:GestureUtils.Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
{ title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType:GestureUtils.Gestures.Line, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
@@ -680,8 +686,8 @@ export class CurrentUserUtils {
static schemaTools():Button[] {
return [
- {title: "Show preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} },
- {title: "Single Lines", toolTip: "Single Line Rows", btnType: ButtonType.ToggleButton, buttonText: "Single Line", icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} },
+ {title: "Preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, icon: "portrait", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} },
+ {title: "1 Line",toolTip: "Single Line Rows", btnType: ButtonType.ToggleButton, icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} },
];
}
@@ -692,7 +698,6 @@ export class CurrentUserUtils {
{ title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
];
}
-
static contextMenuTools():Button[] {
return [
{ btnList: new List<string>([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree,
@@ -700,22 +705,22 @@ export class CurrentUserUtils {
CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel,
CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
CollectionViewType.Grid, CollectionViewType.NoteTaking]),
- title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
+ title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
{ title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
- { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected
{ title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: true, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}},
- { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
+ { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected
+ { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
{ title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
{ title: "Num", icon:"", toolTip: "Frame Number (click to toggle edit mode)", btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
{ title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
{ title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
- { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
+ { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
{ title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
{ title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
+ { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
{ title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected
- { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Schema is selected
- { title: "Audio", icon: "microphone", toolTip: "Dictate", btnType: ButtonType.ToggleButton, expertMode: false, ignoreClick: true, scripts: { onClick: 'return toggleRecording(_readOnly_)'}, funcs: { }}
- ];
+ { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Schema is selected
+ ];
}
/// initializes a context menu button for the top bar context menu
@@ -734,6 +739,7 @@ export class CurrentUserUtils {
return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs);
}
+
static setupContextMenuBtn(params:Button, menuDoc:Doc):Doc {
const menuBtnDoc = DocListCast(menuDoc?.data).find(doc => doc.title === params.title);
const subMenu = params.subMenu;
@@ -743,7 +749,7 @@ export class CurrentUserUtils {
// linear view
const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
- linearView_SubMenu: true, linearView_Expandable: params.btnType !== ButtonType.MultiToggleButton};
+ linearView_SubMenu: true, linearView_Expandable: true};
const items = (menuBtnDoc?:Doc) => !menuBtnDoc ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) );
const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList;
@@ -760,6 +766,32 @@ export class CurrentUserUtils {
const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) );
return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns);
}
+ /// Initializes all the default buttons for the top bar context menu
+ static setupTopbarButtons(doc: Doc, field="myTopBarBtns") {
+ Doc.UserDoc().currentRecording = undefined;
+ Doc.UserDoc().workspaceRecordingState = undefined;
+ Doc.UserDoc().workspaceReplayingState = undefined;
+ const dockedBtns = DocCast(doc[field]);
+ const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:any}) =>
+ DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ??
+ CurrentUserUtils.createToolButton(opts), scripts, funcs);
+
+ const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
+ { opts: { title: "Replicate",icon:"camera",toolTip: "Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true}, scripts: { onClick: `snapshotDashboard()`}},
+ { opts: { title: "Recordings", toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,expertMode: false, ignoreClick: true, width: 100}, funcs: {hidden: `false`, btnList:`getWorkspaceRecordings()`}, scripts: { script: `{ return replayWorkspace(value, _readOnly_); }`, onDragScript: `{ return startRecordingDrag(value); }`}},
+ { opts: { title: "Stop Rec",icon: "stop", toolTip: "Stop recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `!isWorkspaceRecording()`}, scripts: { onClick: `stopWorkspaceRecording()`}},
+ { opts: { title: "Play", icon: "play", toolTip: "Play recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Pause", icon: "pause",toolTip: "Pause playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Playing}"`}, scripts: { onClick: `pauseWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Stop", icon: "stop", toolTip: "Stop playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `stopWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Delete", icon: "trash",toolTip: "delete selected rec", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `removeWorkspaceReplaying(getCurrentRecording())`}}
+ ];
+ const btns = btnDescs.map(desc => dockBtn({_width: desc.opts.width??30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts, desc.funcs));
+ const dockBtnsReqdOpts:DocumentOptions = {
+ title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move',
+ childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: false, ignoreClick: true
+ };
+ return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
+ }
/// collection of documents rendered in the overlay layer above all tabs and other UI
static setupOverlays(doc: Doc, field = "myOverlayDocs") {
@@ -788,7 +820,7 @@ export class CurrentUserUtils {
// When the user views one of these documents, it will be added to the sharing documents 'viewed' list field
// The sharing document also stores the user's color value which helps distinguish shared documents from personal documents
static setupSharedDocs(doc: Doc, sharingDocumentId: string) {
- const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}";
+ const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(documentView.props.treeViewDoc, 'viewed', documentView.rootDoc);}";
const sharedScripts = { treeView_ChildDoubleClick: dblClkScript, }
const sharedDocOpts:DocumentOptions = {
@@ -859,7 +891,6 @@ export class CurrentUserUtils {
doc.defaultAclPrivate ?? (doc.defaultAclPrivate = false);
doc.savedFilters ?? (doc.savedFilters = new List<Doc>());
doc.userBackgroundColor ?? (doc.userBackgroundColor = Colors.DARK_GRAY);
- addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${doc.userBackgroundColor} !important` });
doc.userVariantColor ?? (doc.userVariantColor = Colors.MEDIUM_BLUE);
doc.userColor ?? (doc.userColor = Colors.LIGHT_GRAY);
doc.userTheme ?? (doc.userTheme = ColorScheme.Dark);
@@ -873,6 +904,7 @@ export class CurrentUserUtils {
this.setupOverlays(doc); // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard
this.setupPublished(doc); // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box)
this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected
+ this.setupTopbarButtons(doc);
this.setupDockedButtons(doc); // the bottom bar of font icons
this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left
this.setupDocTemplates(doc); // sets up the template menu of templates
@@ -884,10 +916,8 @@ export class CurrentUserUtils {
Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs)
Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed)
- if (doc.activeDashboard instanceof Doc) {
- // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
- doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme;
- }
+ Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org")
+
new LinkManager();
DocServer.CacheNeedsUpdate && setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500);
@@ -992,7 +1022,8 @@ export class CurrentUserUtils {
}
ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs");
+ScriptingGlobals.add(function IsExploreMode() { return DocumentView.ExploreMode; }, "is Dash in exploration mode");
ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
-ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; }); \ No newline at end of file
+ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; });
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index c2827dac7..b9f6059f4 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,9 +1,9 @@
-import { action, computed, observable, ObservableSet, observe, reaction } from 'mobx';
+import { action, computed, observable, ObservableSet, observe } from 'mobx';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { AclAdmin, AclEdit, Animation } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { listSpec } from '../../fields/Schema';
-import { Cast, DocCast, StrCast } from '../../fields/Types';
+import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { AudioField } from '../../fields/URLField';
import { GetEffectiveAcl } from '../../fields/util';
import { CollectionViewType } from '../documents/DocumentTypes';
@@ -11,7 +11,6 @@ import { CollectionDockingView } from '../views/collections/CollectionDockingVie
import { TabDocView } from '../views/collections/TabDocView';
import { LightboxView } from '../views/LightboxView';
import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
-import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
import { KeyValueBox } from '../views/nodes/KeyValueBox';
import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
import { PresBox } from '../views/nodes/trails';
@@ -101,8 +100,6 @@ export class DocumentManager {
})
);
this.LinkAnchorBoxViews.push(view);
- // this.LinkedDocumentViews.forEach(view => console.log(" LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " +
- // view.b.props.Document.title + "/" + view.b.props.LayoutTemplateString));
} else {
this.AddDocumentView(view);
}
@@ -322,9 +319,12 @@ export class DocumentManager {
@action
restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: DocFocusOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
if (viewSpec && docView) {
- if (docView.ComponentView instanceof FormattedTextBox) docView.ComponentView?.focus(viewSpec, options);
+ //if (docView.ComponentView instanceof FormattedTextBox)
+ //viewSpec !== docView.rootDoc &&
+ docView.ComponentView?.focus?.(viewSpec, options);
PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500);
Doc.linkFollowHighlight(viewSpec ? [docView.rootDoc, viewSpec] : docView.rootDoc, undefined, options.effect);
+ if (options.playMedia) docView.ComponentView?.playFrom?.(NumCast(docView.rootDoc._layout_currentTimecode));
if (options.playAudio) DocumentManager.playAudioAnno(docView.rootDoc);
if (options.toggleTarget && (!options.didMove || docView.rootDoc.hidden)) docView.rootDoc.hidden = !docView.rootDoc.hidden;
if (options.effect) docView.rootDoc[Animation] = options.effect;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 831a22866..4f30e92ce 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -8,14 +8,13 @@ import { BoolCast, ScriptCast, StrCast } from '../../fields/Types';
import { emptyFunction, Utils } from '../../Utils';
import { Docs, DocUtils } from '../documents/Documents';
import * as globalCssVariables from '../views/global/globalCssVariables.scss';
-import { Colors } from '../views/global/globalEnums';
import { DocumentView } from '../views/nodes/DocumentView';
import { ScriptingGlobals } from './ScriptingGlobals';
import { SelectionManager } from './SelectionManager';
import { SnappingManager } from './SnappingManager';
import { UndoManager } from './UndoManager';
-export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
+export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'inSame' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
/**
* Initialize drag
@@ -109,9 +108,11 @@ export namespace DragManager {
constructor(dragDoc: Doc[], dropAction?: dropActionType) {
this.draggedDocuments = dragDoc;
this.droppedDocuments = [];
+ this.draggedViews = [];
this.offset = [0, 0];
this.dropAction = dropAction;
}
+ draggedViews: DocumentView[];
draggedDocuments: Doc[];
droppedDocuments: Doc[];
treeViewDoc?: Doc;
@@ -190,6 +191,13 @@ export namespace DragManager {
// drag a document and drop it (or make an embed/copy on drop)
export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, onDropCompleted?: (e?: DragCompleteEvent) => any) {
+ dragData.draggedViews.forEach(
+ action(view => {
+ const ffview = view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ ffview && (ffview.GroupChildDrag = BoolCast(ffview.Document._isGroup));
+ ffview?.setupDragLines(false);
+ })
+ );
const addAudioTag = (dropDoc: any) => {
dropDoc && !dropDoc.author_date && (dropDoc.author_date = new DateField());
dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc);
@@ -197,6 +205,14 @@ export namespace DragManager {
};
const finishDrag = async (e: DragCompleteEvent) => {
const docDragData = e.docDragData;
+ setTimeout(() =>
+ dragData.draggedViews.forEach(
+ action(view => {
+ const ffview = view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ ffview && (ffview.GroupChildDrag = false);
+ })
+ )
+ );
onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails
if (docDragData && !docDragData.droppedDocuments.length) {
docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
@@ -325,7 +341,7 @@ export namespace DragManager {
export let CanEmbed = false;
export let DocDragData: DocumentDragData | undefined;
export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?: string) {
- if (dragData.dropAction === 'none') return;
+ if (dragData.dropAction === 'none' || DocumentView.ExploreMode) return;
DocDragData = dragData as DocumentDragData;
const batch = UndoManager.StartBatch(dragUndoName ?? 'document drag');
eles = eles.filter(e => e);
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index d0f459291..4e32ed67f 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -108,7 +108,8 @@ export namespace InteractionUtils {
opacity: number,
nodefs: boolean,
downHdlr?: (e: React.PointerEvent) => void,
- mask?: boolean
+ mask?: boolean,
+ dropshadow?: string
) {
const pts = shape ? makePolygon(shape, points) : points;
@@ -161,7 +162,6 @@ export namespace InteractionUtils {
<polygon
style={{ stroke: color }}
strokeLinejoin={lineJoin as any}
- strokeDasharray={dashArray}
strokeWidth={(markerStrokeWidth * 2) / 3}
points={`0 ${-markerStrokeWidth * arrowWidthFactor}, ${markerStrokeWidth * arrowNotchFactor} 0, 0 ${markerStrokeWidth * arrowWidthFactor}, ${arrowLengthFactor * markerStrokeWidth} 0`}
/>
@@ -172,6 +172,7 @@ export namespace InteractionUtils {
<Tag
d={bezier ? strpts : undefined}
points={bezier ? undefined : strpts}
+ filter={!dropshadow ? undefined : `drop-shadow(-1px -1px 0px ${dropshadow}) drop-shadow(2px -1px 0px ${dropshadow}) drop-shadow(2px 2px 0px ${dropshadow}) drop-shadow(-1px 2px 0px ${dropshadow})`}
style={{
// filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
fill: fill && fill !== 'transparent' ? fill : 'none',
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index b8fea340f..146eed6c2 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -68,21 +68,23 @@ export class LinkFollower {
? linkDoc.link_anchor_2
: linkDoc.link_anchor_1
) as Doc;
+ const srcAnchor = LinkManager.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
if (target) {
const doFollow = (canToggle?: boolean) => {
const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle);
const options: DocFocusOptions = {
- playAudio: BoolCast(sourceDoc.followLinkAudio),
+ playAudio: BoolCast(srcAnchor.followLinkAudio),
+ playMedia: BoolCast(srcAnchor.followLinkVideo),
toggleTarget,
noSelect: true,
willPan: true,
- willZoomCentered: BoolCast(sourceDoc.followLinkZoom, false),
- zoomTime: NumCast(sourceDoc.followLinkTransitionTime, 500),
- zoomScale: Cast(sourceDoc.followLinkZoomScale, 'number', null),
- easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any,
- openLocation: StrCast(sourceDoc.followLinkLocation, OpenWhere.lightbox) as OpenWhere,
- effect: sourceDoc,
- zoomTextSelections: BoolCast(sourceDoc.followLinkZoomText),
+ willZoomCentered: BoolCast(srcAnchor.followLinkZoom, false),
+ zoomTime: NumCast(srcAnchor.followLinkTransitionTime, 500),
+ zoomScale: Cast(srcAnchor.followLinkZoomScale, 'number', null),
+ easeFunc: StrCast(srcAnchor.followLinkEase, 'ease') as any,
+ openLocation: StrCast(srcAnchor.followLinkLocation, OpenWhere.lightbox) as OpenWhere,
+ effect: srcAnchor,
+ zoomTextSelections: BoolCast(srcAnchor.followLinkZoomText),
};
if (target.type === DocumentType.PRES) {
const containerDocContext = DocumentManager.GetContextPath(sourceDoc, true); // gather all views that affect layout of sourceDoc so we can revert them after playing the rail
@@ -96,7 +98,7 @@ export class LinkFollower {
}
};
let movedTarget = false;
- if (sourceDoc.followLinkLocation === OpenWhere.inParent) {
+ if (srcAnchor.followLinkLocation === OpenWhere.inParent) {
const sourceDocParent = DocCast(sourceDoc.embedContainer);
if (target.embedContainer instanceof Doc && target.embedContainer !== sourceDocParent) {
Doc.RemoveDocFromList(target.embedContainer, Doc.LayoutFieldKey(target.embedContainer), target);
@@ -108,11 +110,11 @@ export class LinkFollower {
}
Doc.SetContainer(target, sourceDocParent);
const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)];
- if (sourceDoc.followLinkXoffset !== undefined && moveTo[0] !== target.x) {
+ if (srcAnchor.followLinkXoffset !== undefined && moveTo[0] !== target.x) {
target.x = moveTo[0];
movedTarget = true;
}
- if (sourceDoc.followLinkYoffset !== undefined && moveTo[1] !== target.y) {
+ if (srcAnchor.followLinkYoffset !== undefined && moveTo[1] !== target.y) {
target.y = moveTo[1];
movedTarget = true;
}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index ef4b21b05..a533fdd1f 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -56,10 +56,12 @@ export class LinkManager {
Promise.all([lproto?.link_anchor_1 as Doc, lproto?.link_anchor_2 as Doc].map(PromiseValue)).then((lAnchs: Opt<Doc>[]) =>
Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) =>
Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then(
- action(lAnchProtoProtos => {
- link && lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].add(link);
- link && lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].add(link);
- })
+ link &&
+ action(lAnchProtoProtos => {
+ Doc.AddDocToList(Doc.UserDoc(), 'links', link);
+ lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].add(link);
+ lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].add(link);
+ })
)
)
)
@@ -145,6 +147,7 @@ export class LinkManager {
};
public addLink(linkDoc: Doc, checkExists = false) {
+ Doc.AddDocToList(Doc.UserDoc(), 'links', linkDoc);
if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) {
Doc.AddDocToList(Doc.LinkDBDoc(), 'data', linkDoc);
setTimeout(DocServer.UPDATE_SERVER_CACHE, 100);
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 78069d323..c8940194c 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -33,7 +33,7 @@ export class RTFMarkup extends React.Component<{}> {
*/
@computed get cheatSheet() {
return (
- <div style={{ background: SettingsManager.Instance?.userBackgroundColor, color: SettingsManager.Instance?.userColor, textAlign: 'initial', height: '100%' }}>
+ <div style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, textAlign: 'initial', height: '100%' }}>
<p>
<b style={{ fontSize: 'larger' }}>{`wiki:phrase`}</b>
{` display wikipedia page for entered text (terminate with carriage return)`}
@@ -137,7 +137,7 @@ export class RTFMarkup extends React.Component<{}> {
render() {
return (
<MainViewModal
- dialogueBoxStyle={{ backgroundColor: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor), padding: '16px' }}
+ dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, padding: '16px' }}
contents={this.cheatSheet}
isDisplayed={this.isOpen}
interactive={true}
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 64aa7ba9b..560d6b30f 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -57,8 +57,8 @@ export namespace SearchUtil {
const hlights = new Set<string>();
SearchUtil.documentKeys(doc).forEach(
key =>
- Field.toString(doc[key] as Field)
- .toLowerCase()
+ (Field.toString(doc[key] as Field) + Field.toScriptString(doc[key] as Field))
+ .toLowerCase() //
.includes(query) && hlights.add(key)
);
blockedKeys.forEach(key => hlights.delete(key));
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index dbf33fcf5..d0f66d124 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -26,9 +26,6 @@ export namespace SelectionManager {
// if doc is not in SelectedDocuments, add it
if (!manager.SelectedViewsMap.get(docView)) {
if (!ctrlPressed) {
- if (LinkManager.currentLink && !LinkManager.Links(docView.rootDoc).includes(LinkManager.currentLink) && docView.rootDoc !== LinkManager.currentLink) {
- LinkManager.currentLink = undefined;
- }
this.DeselectAll();
}
@@ -54,6 +51,8 @@ export namespace SelectionManager {
}
@action
DeselectAll(): void {
+ LinkManager.currentLink = undefined;
+ LinkManager.currentLinkAnchor = undefined;
manager.SelectedSchemaDocument = undefined;
Array.from(manager.SelectedViewsMap.keys()).forEach(dv => dv.props.whenChildContentsActiveChanged(false));
manager.SelectedViewsMap.clear();
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
index 3c7c35a7e..08dbaac5d 100644
--- a/src/client/util/ServerStats.tsx
+++ b/src/client/util/ServerStats.tsx
@@ -46,12 +46,12 @@ export class ServerStats extends React.Component<{}> {
<div
style={{
display: 'flex',
- height: 300,
+ height: '100%',
width: 400,
- background: SettingsManager.Instance?.userBackgroundColor,
+ background: SettingsManager.userBackgroundColor,
opacity: 0.6,
}}>
- <div style={{ width: 300, height: 100, margin: 'auto', display: 'flex', flexDirection: 'column' }}>
+ <div style={{ width: 300, margin: 'auto', display: 'flex', flexDirection: 'column' }}>
{PingManager.Instance.IsBeating ? 'The server connection is active' : 'The server connection has been interrupted.NOTE: Any changes made will appear to persist but will be lost after a browser refreshes.'}
<br />
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 720badd40..dc852596f 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -20,10 +20,9 @@ import { undoBatch } from './UndoManager';
export enum ColorScheme {
Dark = 'Dark',
Light = 'Light',
- CoolBlue = 'Cool Blue',
- Cupcake = 'Cupcake',
- System = 'Match System',
Custom = 'Custom',
+ CoolBlue = 'CoolBlue',
+ Cupcake = 'Cupcake',
}
export enum freeformScrollMode {
@@ -50,7 +49,21 @@ export class SettingsManager extends React.Component<{}> {
constructor(props: {}) {
super(props);
SettingsManager.Instance = this;
+ this.matchSystem();
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
+ if (Doc.UserDoc().userThemeSystem) {
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) this.changeColorScheme(ColorScheme.Dark);
+ if (window.matchMedia('(prefers-color-scheme: light)').matches) this.changeColorScheme(ColorScheme.Light);
+ }
+ // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
+ });
}
+ matchSystem = () => {
+ if (Doc.UserDoc().userThemeSystem) {
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) this.changeColorScheme(ColorScheme.Dark);
+ if (window.matchMedia('(prefers-color-scheme: light)').matches) this.changeColorScheme(ColorScheme.Light);
+ }
+ };
public close = action(() => (this.isOpen = false));
public open = action(() => (this.isOpen = true));
@@ -88,6 +101,10 @@ export class SettingsManager extends React.Component<{}> {
});
@undoBatch switchUserColor = action((color: string) => (Doc.UserDoc().userColor = color));
@undoBatch switchUserVariantColor = action((color: string) => (Doc.UserDoc().userVariantColor = color));
+ @undoBatch userThemeSystemToggle = action(() => {
+ Doc.UserDoc().userThemeSystem = !Doc.UserDoc().userThemeSystem;
+ this.matchSystem();
+ });
@undoBatch playgroundModeToggle = action(() => {
this.playgroundMode = !this.playgroundMode;
if (this.playgroundMode) {
@@ -123,18 +140,11 @@ export class SettingsManager extends React.Component<{}> {
break;
case ColorScheme.Custom:
break;
- case ColorScheme.System:
- default:
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
- e.matches ? ColorScheme.Dark : ColorScheme.Light; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
- });
- break;
}
});
@computed get colorsContent() {
- const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.Cupcake, ColorScheme.CoolBlue, ColorScheme.Custom, ColorScheme.System];
- const schemeMap = ['Light', 'Dark', 'Cupcake', 'Cool Blue', 'Custom', 'Match System'];
+ const schemeMap = Array.from(Object.keys(ColorScheme));
const userTheme = StrCast(Doc.UserDoc().userTheme);
return (
<div style={{ width: '100%' }}>
@@ -142,16 +152,22 @@ export class SettingsManager extends React.Component<{}> {
formLabel="Theme"
size={Size.SMALL}
type={Type.TERT}
+ closeOnSelect={false}
selectedVal={userTheme}
- setSelectedVal={scheme => this.changeColorScheme(scheme as string)}
- items={colorSchemes.map((scheme, i) => ({
- text: schemeMap[i],
+ setSelectedVal={scheme => {
+ this.changeColorScheme(scheme as string);
+ Doc.UserDoc().userThemeSystem = false;
+ }}
+ items={Object.keys(ColorScheme).map((scheme, i) => ({
+ text: schemeMap[i].replace(/([a-z])([A-Z])/, '$1 $2'),
val: scheme,
}))}
dropdownType={DropdownType.SELECT}
color={SettingsManager.userColor}
fillWidth
/>
+ <Toggle formLabel="Match System" toggleType={ToggleType.SWITCH} color={SettingsManager.userColor} toggleStatus={BoolCast(Doc.UserDoc().userThemeSystem)} onClick={this.userThemeSystemToggle} />
+
{userTheme === ColorScheme.Custom && (
<Group formLabel="Custom Theme">
<ColorPicker
@@ -294,6 +310,7 @@ export class SettingsManager extends React.Component<{}> {
},
};
})}
+ closeOnSelect={true}
dropdownType={DropdownType.SELECT}
type={Type.TERT}
selectedVal={StrCast(Doc.UserDoc().fontFamily)}
@@ -373,6 +390,7 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column-content">
<Dropdown
formLabel={'Mode'}
+ closeOnSelect={true}
items={[
{
text: 'Novice',
@@ -403,7 +421,8 @@ export class SettingsManager extends React.Component<{}> {
</div>
<div className="tab-column-content">
<Dropdown
- formLabel={'Scroll Mode'}
+ formLabel="Scroll Mode"
+ closeOnSelect={true}
items={[
{
text: 'Scroll to Pan',
@@ -436,6 +455,7 @@ export class SettingsManager extends React.Component<{}> {
toggleStatus={BoolCast(Doc.defaultAclPrivate)}
onClick={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))}
/>
+ <Toggle toggleType={ToggleType.SWITCH} formLabel={'Enable Sharing UI'} color={SettingsManager.userColor} toggleStatus={BoolCast(Doc.IsSharingEnabled)} onClick={action(() => (Doc.IsSharingEnabled = !Doc.IsSharingEnabled))} />
</div>
</div>
</div>
diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx
index 6a236face..b25d51b41 100644
--- a/src/client/util/reportManager/ReportManager.tsx
+++ b/src/client/util/reportManager/ReportManager.tsx
@@ -308,6 +308,7 @@ export class ReportManager extends React.Component<{}> {
<Dropdown
color={StrCast(Doc.UserDoc().userColor)}
formLabel={'Type'}
+ closeOnSelect={true}
items={bugDropdownItems}
selectedVal={this.formData.type}
setSelectedVal={val => {
@@ -320,6 +321,7 @@ export class ReportManager extends React.Component<{}> {
<Dropdown
color={StrCast(Doc.UserDoc().userColor)}
formLabel={'Priority'}
+ closeOnSelect={true}
items={priorityDropdownItems}
selectedVal={this.formData.priority}
setSelectedVal={val => {
@@ -330,25 +332,15 @@ export class ReportManager extends React.Component<{}> {
fillWidth
/>
</div>
- <Dropzone
- onDrop={this.onDrop}
- accept={{
- 'image/*': ['.png', '.jpg', '.jpeg', '.gif'],
- 'video/*': ['.mp4', '.mpeg', '.webm', '.mov'],
- 'audio/mpeg': ['.mp3'],
- 'audio/wav': ['.wav'],
- 'audio/ogg': ['.ogg'],
- }}>
- {({ getRootProps, getInputProps }) => (
- <div {...getRootProps({ className: 'dropzone' })} style={{ borderColor: isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.border : lightColors.border }}>
- <input {...getInputProps()} />
- <div className="dropzone-instructions">
- <AiOutlineUpload size={25} />
- <p>Drop or select media that shows the bug (optional)</p>
- </div>
- </div>
- )}
- </Dropzone>
+ <input
+ type="file"
+ accept="image/*, video/*, audio/*"
+ multiple
+ onChange={e => {
+ if (!e.target.files) return;
+ this.setFormData({ ...this.formData, mediaFiles: [...this.formData.mediaFiles, ...Array.from(e.target.files).map(file => ({ _id: v4(), file }))] });
+ }}
+ />
{this.formData.mediaFiles.length > 0 && <ul className="file-list">{this.formData.mediaFiles.map(file => this.getMediaPreview(file))}</ul>}
{this.submitting ? (
<Button
diff --git a/src/client/util/reportManager/reportManagerUtils.ts b/src/client/util/reportManager/reportManagerUtils.ts
index b95417aa1..22e5eebbb 100644
--- a/src/client/util/reportManager/reportManagerUtils.ts
+++ b/src/client/util/reportManager/reportManagerUtils.ts
@@ -88,8 +88,13 @@ export const fileLinktoServerLink = (fileLink: string): string => {
const regex = 'public';
const publicIndex = fileLink.indexOf(regex) + regex.length;
+ let finalUrl: string = '';
+ if (fileLink.includes('.png') || fileLink.includes('.jpg') || fileLink.includes('.jpeg') || fileLink.includes('.gif')) {
+ finalUrl = `${serverUrl}${fileLink.substring(publicIndex + 1).replace('.', '_l.')}`;
+ } else {
+ finalUrl = `${serverUrl}${fileLink.substring(publicIndex + 1)}`;
+ }
- const finalUrl = `${serverUrl}${fileLink.substring(publicIndex + 1).replace('.', '_l.')}`;
return finalUrl;
};
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index c41ea7053..16e76694d 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -3,6 +3,7 @@ import { observable, action, runInAction } from 'mobx';
import './AntimodeMenu.scss';
import { StrCast } from '../../fields/Types';
import { Doc } from '../../fields/Doc';
+import { SettingsManager } from '../util/SettingsManager';
export interface AntimodeMenuProps {}
/**
@@ -150,7 +151,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Co
left: this._left,
top: this._top,
opacity: this._opacity,
- background: StrCast(Doc.UserDoc().userBackgroundColor),
+ background: SettingsManager.userBackgroundColor,
transitionProperty: this._transitionProperty,
transitionDuration: this._transitionDuration,
transitionDelay: this._transitionDelay,
@@ -176,7 +177,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Co
height: 'inherit',
width: 200,
opacity: this._opacity,
- background: StrCast(Doc.UserDoc().userBackgroundColor),
+ background: SettingsManager.userBackgroundColor,
transitionProperty: this._transitionProperty,
transitionDuration: this._transitionDuration,
transitionDelay: this._transitionDelay,
@@ -199,7 +200,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Co
left: this._left,
top: this._top,
opacity: this._opacity,
- background: StrCast(Doc.UserDoc().userBackgroundColor),
+ background: SettingsManager.userBackgroundColor,
transitionProperty: this._transitionProperty,
transitionDuration: this._transitionDuration,
transitionDelay: this._transitionDelay,
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 8412a9aae..adefc7e9c 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -7,6 +7,7 @@ import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextM
import { Utils } from '../../Utils';
import { StrCast } from '../../fields/Types';
import { Doc } from '../../fields/Doc';
+import { SettingsManager } from '../util/SettingsManager';
@observer
export class ContextMenu extends React.Component {
@@ -195,7 +196,7 @@ export class ContextMenu extends React.Component {
<div
className="contextMenu-group"
style={{
- background: StrCast(Doc.UserDoc().userVariantColor),
+ background: StrCast(SettingsManager.userVariantColor),
}}>
<div className="contextMenu-description">{value.join(' -> ')}</div>
</div>
@@ -222,8 +223,8 @@ export class ContextMenu extends React.Component {
style={{
left: this.pageX,
...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }),
- background: StrCast(Doc.UserDoc().userBackgroundColor),
- color: StrCast(Doc.UserDoc().userColor),
+ background: SettingsManager.userBackgroundColor,
+ color: SettingsManager.userColor,
}}>
{!this.itemsNeedSearch ? null : (
<span className={'search-icon'}>
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 4387c6e96..014a6358f 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -1,10 +1,10 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, ColorPicker, EditableText, FontSize, IconButton, Size, Type } from 'browndash-components';
+import { Button, ColorPicker, EditableText, Size, Type } from 'browndash-components';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { FaPlus } from 'react-icons/fa';
-import { Doc, DocListCast, DocListCastAsync } from '../../fields/Doc';
+import { Doc, DocListCast } from '../../fields/Doc';
import { AclPrivate, DocAcl } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
@@ -14,11 +14,11 @@ import { ScriptField } from '../../fields/ScriptField';
import { Cast, ImageCast, StrCast } from '../../fields/Types';
import { DocServer } from '../DocServer';
import { Docs, DocumentOptions, DocUtils } from '../documents/Documents';
-import { CollectionViewType } from '../documents/DocumentTypes';
import { HistoryUtil } from '../util/History';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
-import { undoBatch } from '../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { CollectionView } from './collections/CollectionView';
import { ContextMenu } from './ContextMenu';
@@ -36,28 +36,16 @@ enum DashboardGroup {
@observer
export class DashboardView extends React.Component {
- //TODO: delete dashboard, share dashboard, etc.
-
public static _urlState: HistoryUtil.DocUrl;
+ @observable private openModal = false;
@observable private selectedDashboardGroup = DashboardGroup.MyDashboards;
-
- @observable private newDashboardName: string | undefined = undefined;
- @observable private newDashboardColor: string | undefined = '#AFAFAF';
- @action abortCreateNewDashboard = () => {
- this.newDashboardName = undefined;
- };
- @action setNewDashboardName = (name: string) => {
- this.newDashboardName = name;
- };
- @action setNewDashboardColor = (color: string) => {
- this.newDashboardColor = color;
- };
-
- @action
- selectDashboardGroup = (group: DashboardGroup) => {
- this.selectedDashboardGroup = group;
- };
+ @observable private newDashboardName = '';
+ @observable private newDashboardColor = '#AFAFAF';
+ @action abortCreateNewDashboard = () => (this.openModal = false);
+ @action setNewDashboardName = (name: string) => (this.newDashboardName = name);
+ @action setNewDashboardColor = (color: string) => (this.newDashboardColor = color);
+ @action selectDashboardGroup = (group: DashboardGroup) => (this.selectedDashboardGroup = group);
clickDashboard = (e: React.MouseEvent, dashboard: Doc) => {
if (this.selectedDashboardGroup === DashboardGroup.SharedDashboards) {
@@ -69,45 +57,31 @@ export class DashboardView extends React.Component {
};
getDashboards = (whichGroup: DashboardGroup) => {
- const allDashboards = DocListCast(Doc.MyDashboards.data);
if (whichGroup === DashboardGroup.MyDashboards) {
- return allDashboards.filter(dashboard => Doc.GetProto(dashboard).author === Doc.CurrentUserEmail);
+ return DocListCast(Doc.MyDashboards.data).filter(dashboard => Doc.GetProto(dashboard).author === Doc.CurrentUserEmail);
}
- const sharedDashboards = DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig);
- return sharedDashboards;
- };
-
- isUnviewedSharedDashboard = (dashboard: Doc): boolean => {
- // const sharedDashboards = DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc._type_collection === CollectionViewType.Docking);
- return !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard);
+ return DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig);
};
- getSharedDashboards = () => {
- const sharedDashs = DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc._type_collection === CollectionViewType.Docking);
- return sharedDashs.filter(dashboard => !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard));
- };
+ isUnviewedSharedDashboard = (dashboard: Doc) => !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard);
@undoBatch
- createNewDashboard = async (name: string, background?: string) => {
- setTimeout(() => {
- this.abortCreateNewDashboard();
- }, 100);
+ createNewDashboard = (name: string, background?: string) => {
DashboardView.createNewDashboard(undefined, name, background);
+ this.abortCreateNewDashboard();
};
@computed
get namingInterface() {
- const dashboardCount = DocListCast(Doc.MyDashboards.data).length + 1;
- const placeholder = `Dashboard ${dashboardCount}`;
return (
<div
className="new-dashboard"
style={{
- background: StrCast(Doc.UserDoc().userBackgroundColor),
- color: StrCast(Doc.UserDoc().userColor),
+ background: SettingsManager.userBackgroundColor,
+ color: SettingsManager.userColor,
}}>
<div className="header">Create New Dashboard</div>
- <EditableText formLabel="Title" placeholder={placeholder} type={Type.SEC} color={StrCast(Doc.UserDoc().userColor)} setVal={val => this.setNewDashboardName(val as string)} fillWidth />
+ <EditableText formLabel="Title" placeholder={this.newDashboardName} type={Type.SEC} color={SettingsManager.userColor} setVal={val => this.setNewDashboardName(val as string)} fillWidth />
<ColorPicker
formLabel="Background" //
colorPickerType="github"
@@ -117,63 +91,57 @@ export class DashboardView extends React.Component {
setSelectedColor={this.setNewDashboardColor}
/>
<div className="button-bar">
- <Button text="Cancel" color={StrCast(Doc.UserDoc().userColor)} onClick={this.abortCreateNewDashboard} />
- <Button type={Type.TERT} text="Create" color={StrCast(Doc.UserDoc().userVariantColor)} onClick={() => this.createNewDashboard(this.newDashboardName!, this.newDashboardColor)} />
+ <Button text="Cancel" color={SettingsManager.userColor} onClick={this.abortCreateNewDashboard} />
+ <Button text="Create" color={SettingsManager.userVariantColor} type={Type.TERT} onClick={() => this.createNewDashboard(this.newDashboardName, this.newDashboardColor)} />
</div>
</div>
);
}
+ @action
+ openNewDashboardModal = () => {
+ this.openModal = true;
+ this.setNewDashboardName(`Dashboard ${DocListCast(Doc.MyDashboards.data).length + 1}`);
+ };
_downX: number = 0;
_downY: number = 0;
- @action
- onContextMenu = (dashboard: Doc, e?: React.MouseEvent, pageX?: number, pageY?: number) => {
+ onContextMenu = (dashboard: Doc, e: React.MouseEvent) => {
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
- if (e) {
+ if (navigator.userAgent.includes('Mozilla') || (Math.abs(this._downX - e.clientX) < 3 && Math.abs(this._downY - e.clientY) < 3)) {
e.preventDefault();
e.stopPropagation();
- e.persist();
- if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) {
- return;
- }
- const cm = ContextMenu.Instance;
- cm.addItem({
- description: 'Share Dashboard',
- event: async () => {
- SharingManager.Instance.open(undefined, dashboard);
- },
+ ContextMenu.Instance.addItem({
+ description: `Share Dashboard`,
+ event: () => SharingManager.Instance.open(undefined, dashboard),
icon: 'edit',
});
- cm.addItem({
- description: 'Delete Dashboard',
- event: async () => {
- DashboardView.removeDashboard(dashboard);
- },
+ ContextMenu.Instance.addItem({
+ description: `Delete Dashboard ${Doc.noviceMode ? '(disabled)' : ''}`,
+ event: () => !Doc.noviceMode && DashboardView.removeDashboard(dashboard),
icon: 'trash',
});
- cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
}
};
render() {
- const color = StrCast(Doc.UserDoc().userColor);
- const variant = StrCast(Doc.UserDoc().userVariantColor);
+ const color = SettingsManager.userColor;
+ const variant = SettingsManager.userVariantColor;
return (
<>
<div className="dashboard-view">
<div className="left-menu">
- <Button text={'My Dashboards'} active={this.selectedDashboardGroup === DashboardGroup.MyDashboards} color={color} align={'flex-start'} onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)} fillWidth />
+ <Button text="My Dashboards" active={this.selectedDashboardGroup === DashboardGroup.MyDashboards} color={color} align={'flex-start'} onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)} fillWidth />
<Button
text={'Shared Dashboards' + ' (' + this.getDashboards(DashboardGroup.SharedDashboards).length + ')'}
active={this.selectedDashboardGroup === DashboardGroup.SharedDashboards}
color={this.getDashboards(DashboardGroup.SharedDashboards).some(dash => !DocListCast(Doc.MySharedDocs.viewed).includes(dash)) ? 'green' : color}
- align={'flex-start'}
+ align="flex-start"
onClick={() => this.selectDashboardGroup(DashboardGroup.SharedDashboards)}
fillWidth
/>
- {}
- <Button icon={<FaPlus />} color={variant} iconPlacement="left" text="New Dashboard" type={Type.TERT} onClick={() => this.setNewDashboardName('')} />
+ <Button icon={<FaPlus />} color={variant} iconPlacement="left" text="New Dashboard" type={Type.TERT} onClick={this.openNewDashboardModal} />
</div>
<div className="all-dashboards">
{this.selectedDashboardGroup === DashboardGroup.SharedDashboards && !this.getDashboards(this.selectedDashboardGroup).length
@@ -205,18 +173,14 @@ export class DashboardView extends React.Component {
this._downX = e.clientX;
this._downY = e.clientY;
}}
- onClick={e => {
- e.preventDefault();
- e.stopPropagation();
- this.onContextMenu(dashboard, e);
- }}>
+ onClick={e => this.onContextMenu(dashboard, e)}>
<Button size={Size.SMALL} color={color} icon={<FontAwesomeIcon color={color} icon="bars" />} />
</div>
</div>
<div
- className={`background`}
+ className="background"
style={{
- background: StrCast(Doc.UserDoc().userColor),
+ background: SettingsManager.userColor,
filter: 'opacity(0.2)',
}}
/>
@@ -225,16 +189,12 @@ export class DashboardView extends React.Component {
);
})}
{this.selectedDashboardGroup === DashboardGroup.SharedDashboards ? null : (
- <div
- className="dashboard-container-new"
- onClick={() => {
- this.setNewDashboardName('');
- }}>
+ <div className="dashboard-container-new" onClick={this.openNewDashboardModal}>
+
<div
- className={`background`}
+ className="background"
style={{
- background: StrCast(Doc.UserDoc().userColor),
+ background: SettingsManager.userColor,
filter: 'opacity(0.2)',
}}
/>
@@ -242,13 +202,7 @@ export class DashboardView extends React.Component {
)}
</div>
</div>
- <MainViewModal
- contents={this.namingInterface}
- isDisplayed={this.newDashboardName !== undefined}
- interactive={true}
- closeOnExternalClick={this.abortCreateNewDashboard}
- dialogueBoxStyle={{ width: '400px', height: '180px', color: Colors.LIGHT_GRAY }}
- />
+ <MainViewModal contents={this.namingInterface} isDisplayed={this.openModal} interactive={true} closeOnExternalClick={this.abortCreateNewDashboard} dialogueBoxStyle={{ width: '400px', height: '180px', color: Colors.LIGHT_GRAY }} />
</>
);
}
@@ -270,6 +224,7 @@ export class DashboardView extends React.Component {
public static openDashboard = (doc: Doc | undefined, fromHistory = false) => {
if (!doc) return false;
Doc.AddDocToList(Doc.MyDashboards, 'data', doc);
+ Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', doc);
// this has the side-effect of setting the main container since we're assigning the active/guest dashboard
Doc.UserDoc() ? (Doc.ActiveDashboard = doc) : (Doc.GuestDashboard = doc);
@@ -304,13 +259,14 @@ export class DashboardView extends React.Component {
return true;
};
- public static removeDashboard = async (dashboard: Doc) => {
- const dashboards = await DocListCastAsync(Doc.MyDashboards.data);
- if (dashboards?.length) {
- if (dashboard === Doc.ActiveDashboard) DashboardView.openDashboard(dashboards.find(doc => doc !== dashboard));
+ public static removeDashboard = (dashboard: Doc) => {
+ const dashboards = DocListCast(Doc.MyDashboards.data).filter(dash => dash !== dashboard);
+ undoable(() => {
+ if (dashboard === Doc.ActiveDashboard) DashboardView.openDashboard(dashboards.lastElement());
Doc.RemoveDocFromList(Doc.MyDashboards, 'data', dashboard);
+ Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashboard, undefined, true, true);
if (!dashboards.length) Doc.ActivePage = 'home';
- }
+ }, 'remove dashboard')();
};
public static resetDashboard = (dashboard: Doc) => {
@@ -413,7 +369,9 @@ export class DashboardView extends React.Component {
const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row');
+ Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc);
dashboardDoc['pane-count'] = 1;
+ freeformDoc.embedContainer = dashboardDoc;
Doc.AddDocToList(Doc.MyDashboards, 'data', dashboardDoc);
@@ -493,6 +451,8 @@ ScriptingGlobals.add(function resetDashboard(dashboard: Doc) {
ScriptingGlobals.add(function addToDashboards(dashboard: Doc) {
DashboardView.openDashboard(Doc.MakeEmbedding(dashboard));
}, 'adds Dashboard to set of Dashboards');
-ScriptingGlobals.add(function snapshotDashboard() {
- DashboardView.snapshotDashboard();
+ScriptingGlobals.add(async function snapshotDashboard() {
+ const batch = UndoManager.StartBatch('snapshot');
+ await DashboardView.snapshotDashboard();
+ batch.end();
}, 'creates a snapshot copy of a dashboard');
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index e076e69ca..483b92957 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -139,7 +139,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
const indocs = doc instanceof Doc ? [doc] : doc;
const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
- docs.forEach(doc => doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true));
+ // docs.forEach(doc => doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true));
const targetDataDoc = this.dataDoc;
const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
const toRemove = value.filter(v => docs.includes(v));
@@ -194,6 +194,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
added.forEach(doc => {
doc._dragOnlyWithinContainer = undefined;
if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc;
+ else Doc.GetProto(doc).annotationOn = undefined;
Doc.SetContainer(doc, this.rootDoc);
inheritParentAcls(targetDataDoc, doc, true);
});
@@ -201,7 +202,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add)));
else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
- targetDataDoc[(annotationKey ?? this.annotationKey) + '_modificationDate'] = new DateField(new Date(Date.now()));
+ targetDataDoc[(annotationKey ?? this.annotationKey) + '_modificationDate'] = new DateField();
}
}
return true;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 345135a1a..c1ec5b4a4 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -562,7 +562,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const rootView = this.props.views()[0];
const rootDoc = rootView?.rootDoc;
if (rootDoc) {
- const anchor = rootView.ComponentView?.getAnchor?.(false) ?? rootDoc;
+ const anchor = rootView.ComponentView?.getAnchor?.(true) ?? rootDoc;
const trail = DocCast(anchor.presentationTrail) ?? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail), true);
if (trail !== anchor.presentationTrail) {
DocUtils.MakeLink(anchor, trail, { link_relationship: 'link trail' });
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3018dad79..40eb1fe2b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -90,7 +90,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@computed
get Bounds() {
- if (LinkFollower.IsFollowing) return { x: 0, y: 0, r: 0, b: 0 };
+ if (LinkFollower.IsFollowing || DocumentView.ExploreMode) return { x: 0, y: 0, r: 0, b: 0 };
const views = SelectionManager.Views();
return views
.filter(dv => dv.props.renderDepth > 0)
@@ -262,6 +262,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
}
});
if (!this._iconifyBatch) {
+ (document.activeElement as any).blur?.();
this._iconifyBatch = UndoManager.StartBatch(forceDeleteOrIconify ? 'delete selected docs' : 'iconifying');
} else {
forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes
@@ -804,8 +805,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
</Tooltip>
);
- const colorScheme = StrCast(Doc.ActiveDashboard?.colorScheme);
-
const leftBounds = this.props.boundsLeft;
const topBounds = LightboxView.LightboxDoc ? 0 : this.props.boundsTop;
bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
@@ -818,7 +817,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
const useRotation = !hideResizers && seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.props.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
const rotation = SelectionManager.Views().length == 1 ? NumCast(seldocview.rootDoc._rotation) : 0;
- const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : '';
+ const resizerScheme = '';
// Radius constants
const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
@@ -828,13 +827,14 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
const radiusHandle = (borderRadius / docMax) * maxDist;
const radiusHandleLocation = Math.min(radiusHandle, maxDist);
- const sharingMenu = docShareMode ? (
- <div className="documentDecorations-share">
- <div className={`documentDecorations-share${shareMode}`}>
- &nbsp;
- {shareSymbolIcon + ' ' + shareMode}
- &nbsp;
- {/* {!Doc.noviceMode ? (
+ const sharingMenu =
+ Doc.IsSharingEnabled && docShareMode ? (
+ <div className="documentDecorations-share">
+ <div className={`documentDecorations-share${shareMode}`}>
+ &nbsp;
+ {shareSymbolIcon + ' ' + shareMode}
+ &nbsp;
+ {/* {!Doc.noviceMode ? (
<div className="checkbox">
<div className="checkbox-box">
<input type="checkbox" checked={this.showLayoutAcl} onChange={action(() => (this.showLayoutAcl = !this.showLayoutAcl))} />
@@ -843,16 +843,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
</div>
) : null}
&nbsp; */}
+ </div>
</div>
- </div>
- ) : (
- <div />
- );
+ ) : (
+ <div />
+ );
const titleArea = this._editingTitle ? (
<input
ref={this._keyinput}
- className={`documentDecorations-title${colorScheme}`}
+ className="documentDecorations-title"
type="text"
name="dynbox"
autoComplete="on"
@@ -870,7 +870,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
e.stopPropagation;
}}>
{hideTitle ? null : (
- <span className={`documentDecorations-titleSpan${colorScheme}`} onPointerDown={this.onTitleDown}>
+ <span className="documentDecorations-titleSpan" onPointerDown={this.onTitleDown}>
{this.selectionTitle}
</span>
)}
@@ -886,7 +886,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
);
return (
- <div className={`documentDecorations${colorScheme}`} style={{ display: this._showNothing ? 'none' : undefined }}>
+ <div className="documentDecorations" style={{ display: this._showNothing ? 'none' : undefined }}>
<div
className="documentDecorations-background"
style={{
diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss
index 0955ba8ff..f7c03caf9 100644
--- a/src/client/views/EditableView.scss
+++ b/src/client/views/EditableView.scss
@@ -3,7 +3,8 @@
overflow-wrap: break-word;
word-wrap: break-word;
hyphens: auto;
- overflow: hidden;
+ overflow: auto;
+ height: 100%;
min-width: 20;
text-overflow: ellipsis;
}
@@ -11,6 +12,7 @@
.editableView-container-editing-oneLine {
width: 100%;
height: max-content;
+ overflow: hidden;
span {
p {
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 147921596..ca4ffaf3a 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -231,7 +231,7 @@ export class EditableView extends React.Component<EditableProps> {
onChange: this.props.autosuggestProps.onChange,
}}
/>
- ) : (
+ ) : this.props.oneLine !== false && this.props.GetValue()?.toString().indexOf('\n') === -1 ? (
<input
className="editableView-input"
ref={r => (this._inputref = r)}
@@ -247,31 +247,31 @@ export class EditableView extends React.Component<EditableProps> {
onClick={this.stopPropagation}
onPointerUp={this.stopPropagation}
/>
+ ) : (
+ <textarea
+ className="editableView-input"
+ ref={r => (this._inputref = r)}
+ style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minHeight: `min(100%, ${(this.props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, background: this.props.background }}
+ placeholder={this.props.placeholder}
+ onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
+ defaultValue={this.props.GetValue()}
+ autoFocus={true}
+ onChange={this.onChange}
+ onKeyDown={this.onKeyDown}
+ onKeyPress={this.stopPropagation}
+ onPointerDown={this.stopPropagation}
+ onClick={this.stopPropagation}
+ onPointerUp={this.stopPropagation}
+ />
);
- // ) : (
- // <textarea
- // className="editableView-input"
- // ref={r => (this._inputref = r)}
- // style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minHeight: `min(100%, ${(this.props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, background: this.props.background }}
- // placeholder={this.props.placeholder}
- // onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
- // defaultValue={this.props.GetValue()}
- // autoFocus={true}
- // onChange={this.onChange}
- // onKeyDown={this.onKeyDown}
- // onKeyPress={this.stopPropagation}
- // onPointerDown={this.stopPropagation}
- // onClick={this.stopPropagation}
- // onPointerUp={this.stopPropagation}
- // />
- // );
}
render() {
- if (this._editing && this.props.GetValue() !== undefined) {
+ const gval = this.props.GetValue()?.replace(/\n/g, '\\r\\n');
+ if (this._editing && gval !== undefined) {
return this.props.sizeToContent ? (
<div style={{ display: 'grid', minWidth: 100 }}>
- <div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{this.props.GetValue()}</div>
+ <div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{gval}</div>
{this.renderEditor()}
</div>
) : (
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 0d4f4df5a..69ceb0f65 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -16,6 +16,8 @@ import './FilterPanel.scss';
import { FieldView } from './nodes/FieldView';
import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components';
import { SettingsManager } from '../util/SettingsManager';
+import { Id } from '../../fields/FieldSymbols';
+import { List } from '../../fields/List';
interface filterProps {
rootDoc: Doc;
@@ -55,7 +57,7 @@ export class FilterPanel extends React.Component<filterProps> {
@computed get _allFacets() {
// trace();
- const noviceReqFields = ['author', 'tags', 'text', 'type'];
+ const noviceReqFields = ['author', 'tags', 'text', 'type', '-linkedTo'];
const noviceLayoutFields: string[] = []; //["_layout_curPage"];
const noviceFields = [...noviceReqFields, ...noviceLayoutFields];
@@ -117,7 +119,7 @@ export class FilterPanel extends React.Component<filterProps> {
// }
gatherFieldValues(childDocs: Doc[], facetKey: string) {
- const valueSet = new Set<string>();
+ const valueSet = new Set<string>(StrListCast(this.props.rootDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]));
let rtFields = 0;
let subDocs = childDocs;
if (subDocs.length > 0) {
@@ -126,7 +128,6 @@ export class FilterPanel extends React.Component<filterProps> {
newarray = [];
subDocs.forEach(t => {
const facetVal = t[facetKey];
- // console.log("facetVal " + facetVal)
if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++;
facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field));
(facetVal === true || facetVal == false) && valueSet.add(Field.toString(!facetVal));
@@ -181,14 +182,14 @@ export class FilterPanel extends React.Component<filterProps> {
});
if (facetHeader === 'text') {
- return { facetHeader: facetHeader, renderType: 'text' };
+ return { facetHeader, renderType: 'text' };
} else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) {
const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1));
const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05)));
const ranged = Doc.readDocRangeFilter(this.targetDoc, facetHeader); // not the filter range, but the zooomed in range on the filter
- return { facetHeader: facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged : [extendedMinVal, extendedMaxVal] };
+ return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged : [extendedMinVal, extendedMaxVal] };
} else {
- return { facetHeader: facetHeader, renderType: 'checkbox' };
+ return { facetHeader, renderType: 'checkbox' };
}
})
);
@@ -235,7 +236,7 @@ export class FilterPanel extends React.Component<filterProps> {
facetValues = (facetHeader: string) => {
const allCollectionDocs = new Set<Doc>();
SearchUtil.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
- const set = new Set<string>([String.fromCharCode(127) + '--undefined--']);
+ const set = new Set<string>([...StrListCast(this.props.rootDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]);
if (facetHeader === 'tags')
allCollectionDocs.forEach(child =>
StrListCast(child[facetHeader])
@@ -245,8 +246,11 @@ export class FilterPanel extends React.Component<filterProps> {
else
allCollectionDocs.forEach(child => {
const fieldVal = child[facetHeader] as Field;
- set.add(Field.toString(fieldVal));
- (fieldVal === true || fieldVal === false) && set.add((!fieldVal).toString());
+ if (!(fieldVal instanceof List)) {
+ // currently we have no good way of filtering based on a field that is a list
+ set.add(Field.toString(fieldVal));
+ (fieldVal === true || fieldVal === false) && set.add((!fieldVal).toString());
+ }
});
const facetValues = Array.from(set).filter(v => v);
@@ -257,24 +261,16 @@ export class FilterPanel extends React.Component<filterProps> {
};
render() {
- // console.log('this is frist one today ' + this._allFacets);
- this._allFacets.forEach(element => console.log(element));
- // const options = Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => pair[1].filterable ).map(facet => value: facet, label: facet) //this._allFacets.filter(facet => this.activeFacetHeaders.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet }));
- // console.log('HEELLLLLL ' + DocumentOptions);
-
- let filteredOptions: string[] = ['author', 'tags', 'text', 'acl-Guest'];
+ let filteredOptions: string[] = ['author', 'tags', 'text', 'acl-Guest', ...this._allFacets.filter(facet => facet[0] === facet.charAt(0).toUpperCase())];
Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => {
if (pair[1].filterable) {
filteredOptions.push(pair[0]);
- console.log('THIS IS FILTERABLE ALKDJFIIEII' + filteredOptions);
}
});
let options = filteredOptions.map(facet => ({ value: facet, label: facet }));
- // Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => console.log('this is first piar ' + pair[0] + ' this is second piar ' + pair[1].filterable));
-
return (
<div className="filterBox-treeView">
<div className="filterBox-select">
@@ -349,9 +345,13 @@ export class FilterPanel extends React.Component<filterProps> {
<div
className="filterBox-facetHeader-remove"
onClick={action(e => {
- for (var key of this.facetValues(renderInfo.facetHeader)) {
- if (this.mapActiveFiltersToFacets.get(key)) {
- Doc.setDocFilter(this.targetDoc, renderInfo.facetHeader, key, 'remove');
+ if (renderInfo.facetHeader === 'text') {
+ Doc.setDocFilter(this.targetDoc, renderInfo.facetHeader, 'match', 'remove');
+ } else {
+ for (var key of this.facetValues(renderInfo.facetHeader)) {
+ if (this.mapActiveFiltersToFacets.get(key)) {
+ Doc.setDocFilter(this.targetDoc, renderInfo.facetHeader, key, 'remove');
+ }
}
}
this._selectedFacetHeaders.delete(renderInfo.facetHeader);
@@ -378,15 +378,18 @@ export class FilterPanel extends React.Component<filterProps> {
}
private displayFacetValueFilterUIs(type: string | undefined, facetHeader: string, renderInfoDomain?: number[] | undefined, renderInfoRange?: number[]): React.ReactNode {
- switch (type /* renderInfo.type */) {
- case 'text': // if (this.chosenFacets.get(facetHeader) === 'text')
+ switch (type) {
+ case 'text':
return (
<input
- placeholder={
+ key={this.targetDoc[Id]}
+ placeholder={'enter text to match'}
+ defaultValue={
StrListCast(this.targetDoc._childFilters)
.find(filter => filter.split(Doc.FilterSep)[0] === facetHeader)
- ?.split(Doc.FilterSep)[1] ?? '-empty-'
+ ?.split(Doc.FilterSep)[1]
}
+ style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}
onBlur={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')}
onKeyDown={e => e.key === 'Enter' && undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')(e)}
/>
@@ -398,11 +401,11 @@ export class FilterPanel extends React.Component<filterProps> {
<div>
<input
style={{ width: 20, marginLeft: 20 }}
- checked={
+ checked={['check', 'exists'].includes(
StrListCast(this.targetDoc._childFilters)
.find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue)
- ?.split(Doc.FilterSep)[2] === 'check'
- }
+ ?.split(Doc.FilterSep)[2] ?? ''
+ )}
type={type}
onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')}
/>
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index 71e0ff63c..65f6a6dfa 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -12,7 +12,6 @@ import { UndoManager } from '../util/UndoManager';
import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
-import { DocumentView } from './nodes/DocumentView';
export interface InkHandlesProps {
inkDoc: Doc;
inkView: InkingStroke;
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index 6c213f40f..258bfad66 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -260,7 +260,10 @@ export class InkTranscription extends React.Component {
CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
// TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
const selected = ffView.unprocessedDocs;
- const newCollection = this.groupInkDocs(selected, ffView);
+ const newCollection = this.groupInkDocs(
+ selected.filter(doc => doc.embedContainer),
+ ffView
+ );
ffView.unprocessedDocs = [];
InkTranscription.Instance.transcribeInk(newCollection, selected, false);
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index b3647249a..9b52f5870 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -82,9 +82,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.NativeDimScaling?.() || 1);
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- if (this._subContentView) {
- return this._subContentView.getAnchor?.(addAsAnnotation) || this.rootDoc;
- }
+ const subAnchor = this._subContentView?.getAnchor?.(addAsAnnotation);
+ if (subAnchor !== this.rootDoc && subAnchor) return subAnchor;
if (!addAsAnnotation && !pinProps) return this.rootDoc;
@@ -389,6 +388,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
this.layoutDoc._height = Math.round(NumCast(this.layoutDoc[Height]()));
});
}
+ const highlight = !this.controlUndo && this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
+ const highlightIndex = highlight?.highlightIndex;
+ const highlightColor = (!this.props.isSelected() || !isInkMask) && highlight?.highlightIndex ? highlight?.highlightColor : undefined;
+ const color = StrCast(this.layoutDoc.stroke_outlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent');
// Visually renders the polygonal line made by the user.
const inkLine = InteractionUtils.CreatePolyline(
@@ -411,31 +414,28 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
'',
'none',
1.0,
- false
+ false,
+ undefined,
+ undefined,
+ color === 'transparent' ? highlightColor : undefined
);
- const highlight = !this.controlUndo && this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
- const highlightIndex = highlight?.highlightIndex;
- const highlightColor =
- (!this.props.isSelected() || !isInkMask) && highlight?.highlightIndex
- ? highlight?.highlightColor
- : StrCast(this.layoutDoc.stroke_outlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent');
// Invisible polygonal line that enables the ink to be selected by the user.
const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, mask: boolean = false) =>
InteractionUtils.CreatePolyline(
inkData,
inkLeft,
inkTop,
- mask && highlightColor === 'transparent' ? this.strokeColor : highlightColor,
+ mask && color === 'transparent' ? this.strokeColor : color,
inkStrokeWidth,
- inkStrokeWidth + (fillColor ? (closed ? 2 : (highlightIndex ?? 0) + 2) : 2),
+ inkStrokeWidth + NumCast(this.layoutDoc.stroke_borderWidth) + (fillColor ? (closed ? 2 : (highlightIndex ?? 0) + 2) : 2),
StrCast(this.layoutDoc.stroke_lineJoin),
StrCast(this.layoutDoc.stroke_lineCap),
StrCast(this.layoutDoc.stroke_bezier),
!closed || !fillColor || DashColor(fillColor).alpha() === 0 ? 'none' : fillColor,
- '',
- '',
+ startMarker,
+ endMarker,
markerScale,
- undefined,
+ StrCast(this.layoutDoc.stroke_dash),
inkScaleX,
inkScaleY,
'',
@@ -443,9 +443,9 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
0.0,
false,
downHdlr,
- mask
+ mask,
+ highlightColor
);
- const fsize = +StrCast(this.props.Document._text_fontSize, '12px').replace('px', '');
// bootsrap 3 style sheet sets line height to be 20px for default 14 point font size.
// this attempts to figure out the lineHeight ratio by inquiring the body's lineHeight and dividing by the fontsize which should yield 1.428571429
// see: https://bibwild.wordpress.com/2019/06/10/bootstrap-3-to-4-changes-in-how-font-size-line-height-and-spacing-is-done-or-what-happened-to-line-height-computed/
@@ -492,7 +492,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
yPadding={10}
xPadding={10}
fieldKey="text"
- fontSize={fsize}
dontRegisterView={true}
noSidebar={true}
dontScale={true}
diff --git a/src/client/views/LightboxView.scss b/src/client/views/LightboxView.scss
index f86a1d211..9a9b8a437 100644
--- a/src/client/views/LightboxView.scss
+++ b/src/client/views/LightboxView.scss
@@ -5,7 +5,6 @@
top: 10;
background: transparent;
border-radius: 8;
- color: white;
opacity: 0.7;
width: 25;
flex-direction: column;
@@ -17,11 +16,10 @@
.lightboxView-tabBtn {
margin: auto;
position: absolute;
- right: 38;
+ right: 45;
top: 10;
background: transparent;
border-radius: 8;
- color: white;
opacity: 0.7;
width: 25;
flex-direction: column;
@@ -33,11 +31,10 @@
.lightboxView-penBtn {
margin: auto;
position: absolute;
- right: 70;
+ right: 80;
top: 10;
background: transparent;
border-radius: 8;
- color: white;
opacity: 0.7;
width: 25;
flex-direction: column;
@@ -49,11 +46,10 @@
.lightboxView-exploreBtn {
margin: auto;
position: absolute;
- right: 100;
+ right: 115;
top: 10;
background: transparent;
border-radius: 8;
- color: white;
opacity: 0.7;
width: 25;
flex-direction: column;
@@ -68,7 +64,6 @@
left: 0;
width: 100%;
height: 100%;
- background: #000000bb;
z-index: 1000;
.lightboxView-contents {
position: absolute;
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 8f081b321..93eaec959 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -1,15 +1,17 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { InkTool } from '../../fields/InkField';
-import { Cast, NumCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../Utils';
import { DocUtils } from '../documents/Documents';
import { DocumentManager } from '../util/DocumentManager';
import { LinkManager } from '../util/LinkManager';
import { SelectionManager } from '../util/SelectionManager';
+import { SettingsManager } from '../util/SettingsManager';
import { Transform } from '../util/Transform';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
@@ -118,7 +120,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
bottom,
}}>
- <div className="lightboxView-navBtn" title={color} style={{ top, color: color ? 'red' : 'white', background: color ? 'white' : undefined }} onClick={click}>
+ <div className="lightboxView-navBtn" title={color} style={{ top, color: SettingsManager.userColor, background: undefined }} onClick={click}>
<div style={{ height: 10 }}>{color}</div>
<FontAwesomeIcon icon={icon as any} size="3x" />
</div>
@@ -229,6 +231,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
downx = e.clientX;
downy = e.clientY;
}}
+ style={{ background: SettingsManager.userBackgroundColor }}
onClick={e => {
if (Math.abs(downx - e.clientX) < 4 && Math.abs(downy - e.clientY) < 4) {
LightboxView.SetLightboxDoc(undefined);
@@ -242,6 +245,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
width: this.lightboxWidth(),
height: this.lightboxHeight(),
clipPath: `path('${Doc.UserDoc().renderStyle === 'comic' ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`,
+ background: SettingsManager.userBackgroundColor,
+ color: SettingsManager.userColor,
}}>
{/* <CollectionMenu /> TODO:glr This is where it would go*/}
@@ -299,47 +304,68 @@ export class LightboxView extends React.Component<LightboxViewProps> {
this.future()?.length.toString()
)}
<LightboxTourBtn navBtn={this.navBtn} future={this.future} stepInto={this.stepInto} />
- <div
- className="lightboxView-navBtn"
- title="toggle fit width"
- onClick={e => {
- e.stopPropagation();
- LightboxView.LightboxDoc!._layout_fitWidth = !LightboxView.LightboxDoc!._layout_fitWidth;
- }}>
- <FontAwesomeIcon icon={LightboxView.LightboxDoc?._layout_fitWidth ? 'arrows-alt-h' : 'arrows-alt-v'} size="2x" />
+ <div className="lightboxView-navBtn">
+ <Toggle
+ tooltip="toggle reading view"
+ color={SettingsManager.userColor}
+ background={BoolCast(LightboxView.LightboxDoc!._layout_fitWidth) ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
+ toggleType={ToggleType.BUTTON}
+ type={Type.TERT}
+ toggleStatus={BoolCast(LightboxView.LightboxDoc!._layout_fitWidth)}
+ onClick={e => {
+ e.stopPropagation();
+ LightboxView.LightboxDoc!._layout_fitWidth = !LightboxView.LightboxDoc!._layout_fitWidth;
+ }}
+ icon={<FontAwesomeIcon icon={LightboxView.LightboxDoc?._layout_fitWidth ? 'book-open' : 'book'} size="sm" />}
+ />
</div>
- <div
- className="lightboxView-tabBtn"
- title="open in tab"
- onClick={e => {
- const lightdoc = LightboxView._docTarget || LightboxView._doc!;
- e.stopPropagation();
- Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', lightdoc);
- CollectionDockingView.AddSplit(lightdoc, OpenWhereMod.none);
- SelectionManager.DeselectAll();
- LightboxView.SetLightboxDoc(undefined);
- }}>
- <FontAwesomeIcon icon={'file-download'} size="2x" />
+ <div className="lightboxView-tabBtn">
+ <Toggle
+ tooltip="open document in a tab"
+ color={SettingsManager.userColor}
+ background={SettingsManager.userBackgroundColor}
+ toggleType={ToggleType.BUTTON}
+ type={Type.TERT}
+ icon={<FontAwesomeIcon icon="file-download" size="sm" />}
+ onClick={e => {
+ const lightdoc = LightboxView._docTarget || LightboxView._doc!;
+ e.stopPropagation();
+ Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', lightdoc);
+ CollectionDockingView.AddSplit(lightdoc, OpenWhereMod.none);
+ SelectionManager.DeselectAll();
+ LightboxView.SetLightboxDoc(undefined);
+ }}
+ />
</div>
- <div
- className="lightboxView-penBtn"
- title="toggle pen annotation"
- style={{ background: Doc.ActiveTool === InkTool.Pen ? 'white' : undefined }}
- onClick={e => {
- e.stopPropagation();
- Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen;
- }}>
- <FontAwesomeIcon color={Doc.ActiveTool === InkTool.Pen ? 'black' : 'white'} icon={'pen'} size="2x" />
+ <div className="lightboxView-penBtn">
+ <Toggle
+ tooltip="toggle pen annotation"
+ color={SettingsManager.userColor}
+ background={Doc.ActiveTool === InkTool.Pen ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
+ toggleType={ToggleType.BUTTON}
+ toggleStatus={Doc.ActiveTool === InkTool.Pen}
+ type={Type.TERT}
+ icon={<FontAwesomeIcon icon="pen" size="sm" />}
+ onClick={e => {
+ e.stopPropagation();
+ Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen;
+ }}
+ />
</div>
- <div
- className="lightboxView-exploreBtn"
- title="toggle explore mode to navigate among documents only"
- style={{ background: DocumentView.ExploreMode ? 'white' : undefined }}
- onClick={action(e => {
- e.stopPropagation();
- DocumentView.ExploreMode = !DocumentView.ExploreMode;
- })}>
- <FontAwesomeIcon color={DocumentView.ExploreMode ? 'black' : 'white'} icon={'globe-americas'} size="2x" />
+ <div className="lightboxView-exploreBtn">
+ <Toggle
+ tooltip="toggle explore mode to navigate among documents only"
+ color={SettingsManager.userColor}
+ background={DocumentView.ExploreMode ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
+ toggleType={ToggleType.BUTTON}
+ type={Type.TERT}
+ toggleStatus={DocumentView.ExploreMode}
+ icon={<FontAwesomeIcon icon="globe-americas" size="sm" />}
+ onClick={action(e => {
+ e.stopPropagation();
+ DocumentView.ExploreMode = !DocumentView.ExploreMode;
+ })}
+ />
</div>
</div>
);
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index a04309d0c..96bd52d39 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -14,6 +14,7 @@ import { TrackMovements } from '../util/TrackMovements';
import { CollectionView } from './collections/CollectionView';
import './global/globalScripts';
import { MainView } from './MainView';
+import { BranchingTrailManager } from '../util/BranchingTrailManager';
dotenv.config();
AssignAllExtensions();
@@ -50,6 +51,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
document.cookie = `loadtime=${loading};${expires};path=/`;
new TrackMovements();
new ReplayMovements();
+ new BranchingTrailManager({});
new PingManager();
root.render(<MainView />);
}, 0);
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index c880dde9a..4fb2ac279 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -62,8 +62,7 @@ h1,
pointer-events: none;
}
-.mainView-container,
-.mainView-container-Dark {
+.mainView-container {
width: 100%;
height: 100%;
position: absolute;
@@ -74,58 +73,12 @@ h1,
touch-action: none;
}
-.mainView-container,
-.mainView-container-Dark {
- .lm_header .lm_tab {
- padding: 0px;
- //opacity: 0.7;
- box-shadow: none;
- height: 25px;
- border-bottom: black solid;
- }
-}
-
.mainView-container {
color: $dark-gray;
.lm_goldenlayout {
background: $medium-gray;
}
-
- .lm_title {
- background: $light-gray;
- color: $dark-gray;
- }
-}
-
-.mainView-container-Dark {
- color: $light-gray;
-
- .lm_goldenlayout {
- background: $medium-gray;
- }
-
- .lm_title {
- background: $dark-gray;
- color: unset;
- }
-
- .marquee {
- border-color: $white;
- }
-
- #search-input {
- background: $light-gray;
- }
-
- .contextMenu-cont,
- .contextMenu-item {
- background: $dark-gray;
- }
-
- .contextMenu-item:hover {
- background: $medium-gray;
- }
}
.mainView-dashboardArea {
@@ -143,6 +96,7 @@ h1,
top: 0;
}
+.mainView-propertiesDragger-minified,
.mainView-propertiesDragger {
//background-color: rgb(140, 139, 139);
background-color: $light-gray;
@@ -155,9 +109,9 @@ h1,
border-bottom-left-radius: 10px;
border-right: unset;
z-index: 41; // lm_maximised has a z-index of 40 and this needs to be above that
- display: flex;
align-items: center;
padding: 4px;
+ display: flex;
.mainView-propertiesDragger-icon {
width: 10px;
@@ -171,9 +125,11 @@ h1,
cursor: grab;
}
}
+.mainView-propertiesDragger-minified {
+ width: 10px;
+}
-.mainView-innerContent,
-.mainView-innerContent-Dark {
+.mainView-innerContent {
display: contents;
flex-direction: row;
position: relative;
@@ -206,44 +162,6 @@ h1,
background-color: $light-gray;
}
-.mainView-innerContent-Dark {
- .propertiesView {
- background-color: #252525;
-
- input {
- background-color: $medium-gray;
- }
-
- .propertiesView-sharingTable {
- background-color: $medium-gray;
- }
-
- .editable-title {
- background-color: $medium-gray;
- }
-
- .propertiesView-field {
- background-color: $medium-gray;
- }
- }
-
- .mainView-propertiesDragger,
- .mainView-libraryHandle {
- background: #353535;
- }
-}
-
-.mainView-container-Dark {
- .contextMenu-cont {
- background: $medium-gray;
- color: $white;
-
- input::placeholder {
- color: $white;
- }
- }
-}
-
.mainView-leftMenuPanel {
min-width: var(--menuPanelWidth);
border-right: $standard-border;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index dde04dcc0..0a3389fc2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -10,13 +10,14 @@ import 'normalize.css';
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { DocCast, StrCast } from '../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils';
+import { emptyFunction, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
import { Docs } from '../documents/Documents';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { CaptureManager } from '../util/CaptureManager';
import { DocumentManager } from '../util/DocumentManager';
+import { DragManager } from '../util/DragManager';
import { GroupManager } from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
import { Hypothesis } from '../util/HypothesisUtils';
@@ -111,9 +112,6 @@ export class MainView extends React.Component {
@computed private get userDoc() {
return Doc.UserDoc();
}
- @computed private get colorScheme() {
- return StrCast(Doc.ActiveDashboard?.colorScheme);
- }
@observable mainDoc: Opt<Doc>;
@computed private get mainContainer() {
if (window.location.pathname.startsWith('/doc/') && Doc.CurrentUserEmail === 'guest') {
@@ -143,6 +141,12 @@ export class MainView extends React.Component {
mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
componentDidMount() {
+ const scriptTag = document.createElement('script');
+ scriptTag.setAttribute('type', 'text/javascript');
+ scriptTag.setAttribute('src', 'https://www.bing.com/api/maps/mapcontrol?callback=makeMap');
+ scriptTag.async = true;
+ scriptTag.defer = true;
+ document.body.appendChild(scriptTag);
document.getElementById('root')?.addEventListener('scroll', e => (ele => (ele.scrollLeft = ele.scrollTop = 0))(document.getElementById('root')!));
const ele = document.getElementById('loader');
const prog = document.getElementById('dash-progress');
@@ -696,7 +700,7 @@ export class MainView extends React.Component {
switch (whereFields[0]) {
case OpenWhere.lightbox: return LightboxView.AddDocTab(doc, location);
case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods);
- case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods);
+ case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, undefined, "dontSelectOnActivate"); // bcz: hack! mark the toggle so that it won't be selected on activation- this is needed so that the backlinks menu can toggle views of targets on and off without selecting them
case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, undefined, undefined, keyValue);
}
};
@@ -724,7 +728,6 @@ export class MainView extends React.Component {
PanelHeight={this.leftMenuFlyoutHeight}
renderDepth={0}
isContentActive={returnTrue}
- scriptContext={CollectionDockingView.Instance?.props.Document}
focus={emptyFunction}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
@@ -762,7 +765,6 @@ export class MainView extends React.Component {
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
- scriptContext={this}
/>
</div>
);
@@ -793,7 +795,7 @@ export class MainView extends React.Component {
return (
<>
{this._hideUI ? null : this.leftMenuPanel}
- <div key="inner" className={`mainView-innerContent${this.colorScheme}`}>
+ <div key="inner" className="mainView-innerContent">
{this.flyout}
<div
className="mainView-libraryHandle"
@@ -805,12 +807,20 @@ export class MainView extends React.Component {
{this.dockingContent}
{this._hideUI ? null : (
- <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ background: SettingsManager.userBackgroundColor, right: this.propertiesWidth() - 1 }}>
+ <div
+ className={`mainView-propertiesDragger${this.propertiesWidth() < 10 ? '-minified' : ''}`}
+ key="props"
+ onPointerDown={this.onPropertiesPointerDown}
+ style={{ background: SettingsManager.userVariantColor, right: this.propertiesWidth() - 1 }}>
<FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={SettingsManager.userColor} size="sm" />
</div>
)}
- <div className="properties-container" style={{ width: this.propertiesWidth() }}>
- {this.propertiesWidth() < 10 ? null : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
+ <div className="properties-container" style={{ width: this.propertiesWidth(), color: SettingsManager.userColor }}>
+ {
+ <div style={{ display: this.propertiesWidth() < 10 ? 'none' : undefined }}>
+ <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />
+ </div>
+ }
</div>
</div>
</div>
@@ -832,7 +842,7 @@ export class MainView extends React.Component {
).observe(r);
}}
style={{
- color: this.colorScheme === ColorScheme.Dark ? 'rgb(205,205,205)' : 'black',
+ color: 'black',
height: `calc(100% - ${this.topOfDashUI + this.topMenuHeight()}px)`,
width: '100%',
}}>
@@ -902,19 +912,22 @@ export class MainView extends React.Component {
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
/>
- {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{StrCast(this.userDoc?.presentationMode)}</div> : <></>}
+ {['watching', 'recording'].includes(StrCast(this.userDoc?.presentationMode)) ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{StrCast(this.userDoc?.presentationMode)}</div> : <></>}
</div>
);
}
@computed get snapLines() {
- return !SelectionManager.Views().some(dv => dv.rootDoc.freeform_snapLines) ? null : (
+ SnappingManager.GetIsDragging();
+ const dragged = DragManager.docsBeingDragged.lastElement();
+ const dragPar = dragged ? DocumentManager.Instance.getDocumentView(dragged)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView : undefined;
+ return !dragPar?.rootDoc.freeform_snapLines ? null : (
<div className="mainView-snapLines">
<svg style={{ width: '100%', height: '100%' }}>
{SnappingManager.horizSnapLines().map((l, i) => (
- <line key={i} x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={'1 1'} />
+ <line key={i} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(dragPar.rootDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
))}
{SnappingManager.vertSnapLines().map((l, i) => (
- <line key={i} y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={'1 1'} />
+ <line key={i} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(dragPar.rootDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
))}
</svg>
</div>
@@ -989,7 +1002,7 @@ export class MainView extends React.Component {
render() {
return (
<div
- className={`mainView-container ${this.colorScheme}`}
+ className="mainView-container"
style={{
color: SettingsManager.userColor,
background: SettingsManager.userBackgroundColor,
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index d0a79eb70..67b722e7a 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -3,6 +3,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc } from '../../fields/Doc';
import { StrCast } from '../../fields/Types';
+import { SettingsManager } from '../util/SettingsManager';
import './MainViewModal.scss';
export interface MainViewOverlayProps {
@@ -44,7 +45,7 @@ export class MainViewModal extends React.Component<MainViewOverlayProps> {
className="overlay"
onClick={this.props?.closeOnExternalClick}
style={{
- backgroundColor: isDark(StrCast(Doc.UserDoc().userColor)) ? '#DFDFDF30' : '#32323230',
+ backgroundColor: isDark(SettingsManager.userColor) ? '#DFDFDF30' : '#32323230',
...(p.overlayStyle || {}),
}}
/>
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 3d8d569fa..0987b0867 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -10,7 +10,7 @@ import { unimplementedFunction, Utils } from '../../Utils';
import { Docs, DocUtils } from '../documents/Documents';
import { DragManager } from '../util/DragManager';
import { FollowLinkScript } from '../util/LinkFollower';
-import { undoBatch, UndoManager } from '../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
import './MarqueeAnnotator.scss';
import { DocumentView } from './nodes/DocumentView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
@@ -55,7 +55,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
UndoManager.RunInBatch(() => this.props.anchorMenuCrop?.(this.highlight('', true, undefined, false), true), 'cropping');
}
};
- AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true));
+ AnchorMenu.Instance.OnClick = undoable((e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)), 'make sidebar annotation');
AnchorMenu.Instance.OnAudio = unimplementedFunction;
AnchorMenu.Instance.Highlight = this.highlight;
AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true);
@@ -211,7 +211,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DocData]);
const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
addAsAnnotation && !savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc);
- return (annotationDoc as Doc) ?? undefined;
+ return annotationDoc as Doc;
};
public static previewNewAnnotation = action((savedAnnotations: ObservableMap<number, HTMLDivElement[]>, annotationLayer: HTMLDivElement, div: HTMLDivElement, page: number) => {
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 62aa4d1d4..c174befc0 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,4 +1,3 @@
-
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
@@ -17,6 +16,7 @@ import { LightboxView } from './LightboxView';
import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
import './OverlayView.scss';
import { DefaultStyleProvider } from './StyleProvider';
+const _global = (window /* browser */ || global) /* node */ as any;
export type OverlayDisposer = () => void;
@@ -116,6 +116,20 @@ export class OverlayView extends React.Component {
super(props);
if (!OverlayView.Instance) {
OverlayView.Instance = this;
+ new _global.ResizeObserver(
+ action((entries: any) => {
+ for (const entry of entries) {
+ DocListCast(Doc.MyOverlayDocs?.data).forEach(doc => {
+ if (NumCast(doc.overlayX) > entry.contentRect.width - 10) {
+ doc.overlayX = entry.contentRect.width - 10;
+ }
+ if (NumCast(doc.overlayY) > entry.contentRect.height - 10) {
+ doc.overlayY = entry.contentRect.height - 10;
+ }
+ });
+ }
+ })
+ ).observe(window.document.body);
}
}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 1d88e9ad6..e3a43d45f 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -2,7 +2,6 @@ import { action, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Opt } from '../../fields/Doc';
-import { StrCast } from '../../fields/Types';
import { lightOrDark, returnFalse } from '../../Utils';
import { Docs, DocumentOptions, DocUtils } from '../documents/Documents';
import { ImageUtils } from '../util/Import & Export/ImageUtils';
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 42db0b9be..d1561fd67 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -32,6 +32,7 @@ import { TfiBarChart } from 'react-icons/tfi';
import { CiGrid31 } from 'react-icons/ci';
import { RxWidth } from 'react-icons/rx';
import { Dropdown, DropdownType, IListItemProps, Toggle, ToggleType, Type } from 'browndash-components';
+import { SettingsManager } from '../util/SettingsManager';
enum UtilityButtonState {
Default,
@@ -56,8 +57,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
return !targetDoc ? null : (
<Toggle
toggleStatus={BoolCast(targetDoc[property])}
+ tooltip={tooltip(BoolCast(targetDoc[property]))}
text={label(targetDoc?.[property])}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
icon={icon(targetDoc?.[property] as any)}
iconPlacement={'left'}
align={'flex-start'}
@@ -164,9 +166,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@computed get forceActiveButton() {
//select text
return this.propertyToggleBtn(
- on => (on ? 'INACTIVE INTERACTION' : 'ACTIVE INTERACTION'),
+ on => (on ? 'SELECT TO INTERACT' : 'ALWAYS INTERACTIVE'),
'_forceActive',
- on => `${on ? 'Select to activate' : 'Contents always active'} `,
+ on => `${on ? 'Document must be selected to interact with its contents' : 'Contents always active (respond to click/drag events)'} `,
on => <MdTouchApp /> // 'eye'
);
}
@@ -209,9 +211,12 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@computed get layout_fitWidthButton() {
return this.propertyToggleBtn(
- on => (on ? 'RESTRICT WIDTH' : 'FIT WIDTH'), //'Fit\xA0Width',
+ on => (on ? 'SCALED VIEW' : 'READING VIEW'), //'Fit\xA0Width',
'_layout_fitWidth',
- on => `${on ? "Don't" : 'Do'} fit content to width of container`,
+ on =>
+ on
+ ? "Scale document so it's width and height fit container (no effect when document is viewed on freeform canvas)"
+ : "Scale document so it's width fits container and its height expands/contracts to fit available space (no effect when document is viewed on freeform canvas)",
on => (on ? <AiOutlineColumnWidth /> : <RxWidth />) // 'arrows-alt-h'
);
}
@@ -376,10 +381,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
<Dropdown
tooltip={'Choose onClick behavior'}
items={items}
+ closeOnSelect={true}
selectedVal={this.onClickVal}
setSelectedVal={val => this.handleOptionChange(val as string)}
title={'Choose onClick behaviour'}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
dropdownType={DropdownType.SELECT}
type={Type.SEC}
fillWidth
@@ -511,7 +517,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
{toggle(this.layout_autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })}
{toggle(this.maskButton, { display: !isInk ? 'none' : '' })}
{toggle(this.hideImageButton, { display: !isImage ? 'none' : '' })}
- {toggle(this.chromeButton, { display: !isCollection || isNovice ? 'none' : '' })}
+ {toggle(this.chromeButton, { display: isNovice ? 'none' : '' })}
{toggle(this.gridButton, { display: !isCollection ? 'none' : '' })}
{/* {toggle(this.groupButton, { display: isTabView || !isCollection ? 'none' : '' })} */}
{toggle(this.snapButton, { display: !isCollection ? 'none' : '' })}
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index 91a7b3da3..b1e99c3f5 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -1,3 +1,4 @@
+import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc } from '../../fields/Doc';
@@ -6,6 +7,7 @@ import { DocumentType } from '../documents/DocumentTypes';
import { LinkManager } from '../util/LinkManager';
import { SelectionManager } from '../util/SelectionManager';
import { SettingsManager } from '../util/SettingsManager';
+import { CollectionDockingView } from './collections/CollectionDockingView';
import { LinkMenu } from './linking/LinkMenu';
import { OpenWhere, OpenWhereMod } from './nodes/DocumentView';
import './PropertiesDocBacklinksSelector.scss';
@@ -19,16 +21,17 @@ type PropertiesDocBacklinksSelectorProps = {
@observer
export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDocBacklinksSelectorProps> {
- getOnClick = (link: Doc) => {
+ getOnClick = action((link: Doc) => {
const linkAnchor = this.props.Document;
const other = LinkManager.getOppositeAnchor(link, linkAnchor);
const otherdoc = !other ? undefined : other.annotationOn && other.type !== DocumentType.RTF ? Cast(other.annotationOn, Doc, null) : other;
-
+ LinkManager.currentLink = link;
if (otherdoc) {
otherdoc.hidden = false;
this.props.addDocTab(Doc.IsDataProto(otherdoc) ? Doc.MakeDelegate(otherdoc) : otherdoc, OpenWhere.toggleRight);
+ CollectionDockingView.Instance?.endUndoBatch();
}
- };
+ });
render() {
return !SelectionManager.Views().length ? null : (
diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx
index b72e048df..bd586b2f9 100644
--- a/src/client/views/PropertiesSection.tsx
+++ b/src/client/views/PropertiesSection.tsx
@@ -5,10 +5,11 @@ import { observer } from 'mobx-react';
import './PropertiesSection.scss';
import { Doc } from '../../fields/Doc';
import { StrCast } from '../../fields/Types';
+import { SettingsManager } from '../util/SettingsManager';
export interface PropertiesSectionProps {
title: string;
- content?: JSX.Element | string | null;
+ children?: JSX.Element | string | null;
isOpen: boolean;
setIsOpen: (bool: boolean) => any;
inSection?: boolean;
@@ -19,21 +20,21 @@ export interface PropertiesSectionProps {
@observer
export class PropertiesSection extends React.Component<PropertiesSectionProps> {
@computed get color() {
- return StrCast(Doc.UserDoc().userColor);
+ return SettingsManager.userColor;
}
@computed get backgroundColor() {
- return StrCast(Doc.UserDoc().userBackgroundColor);
+ return SettingsManager.userBackgroundColor;
}
@computed get variantColor() {
- return StrCast(Doc.UserDoc().userVariantColor);
+ return SettingsManager.userVariantColor;
}
@observable isDouble: boolean = false;
render() {
- if (this.props.content === undefined || this.props.content === null) return null;
+ if (this.props.children === undefined || this.props.children === null) return null;
else
return (
<div className="propertiesView-section" onPointerEnter={action(() => this.props.setInSection && this.props.setInSection(true))} onPointerLeave={action(() => this.props.setInSection && this.props.setInSection(false))}>
@@ -58,7 +59,7 @@ export class PropertiesSection extends React.Component<PropertiesSectionProps> {
<FontAwesomeIcon icon={this.props.isOpen ? 'caret-down' : 'caret-right'} size="lg" />
</div>
</div>
- {!this.props.isOpen ? null : <div className="propertiesView-content">{this.props.content}</div>}
+ {!this.props.isOpen ? null : <div className="propertiesView-content">{this.props.children}</div>}
</div>
);
}
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index b116a622c..2da2ec568 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -26,6 +26,12 @@
display: flex;
flex-direction: row;
}
+ .propertiesView-titleExtender {
+ text-overflow: ellipsis;
+ max-width: 100%;
+ white-space: pre;
+ overflow: hidden;
+ }
overflow-x: hidden;
overflow-y: auto;
@@ -40,8 +46,7 @@
}
.propertiesView-info {
- margin-top: 20;
- margin-right: 10;
+ margin-top: -5;
float: right;
font-size: 20;
path {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 5f9439cbc..04a948a27 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -3,14 +3,12 @@ import { IconLookup } from '@fortawesome/fontawesome-svg-core';
import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Checkbox, Tooltip } from '@material-ui/core';
-import { Button, Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components';
+import { Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components';
import { concat } from 'lodash';
-import { Lambda, action, computed, observable } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, trace } from 'mobx';
import { observer } from 'mobx-react';
import { ColorState, SketchPicker } from 'react-color';
import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
-import { GrCircleInformation } from 'react-icons/gr';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
import { AclAdmin, DocAcl, DocData, Height, Width } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
@@ -18,28 +16,29 @@ import { InkField } from '../../fields/InkField';
import { List } from '../../fields/List';
import { ComputedField } from '../../fields/ScriptField';
import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
-import { GetEffectiveAcl, SharingPermissions, normalizeEmail } from '../../fields/util';
-import { DocumentType } from '../documents/DocumentTypes';
+import { GetEffectiveAcl, normalizeEmail, SharingPermissions } from '../../fields/util';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils';
+import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { DocumentManager } from '../util/DocumentManager';
import { GroupManager } from '../util/GroupManager';
import { LinkManager } from '../util/LinkManager';
import { SelectionManager } from '../util/SelectionManager';
+import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
import { Transform } from '../util/Transform';
-import { UndoManager, undoBatch, undoable } from '../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
import { EditableView } from './EditableView';
import { FilterPanel } from './FilterPanel';
import { InkStrokeProperties } from './InkStrokeProperties';
+import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
+import { KeyValueBox } from './nodes/KeyValueBox';
+import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
import { PropertiesButtons } from './PropertiesButtons';
import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector';
import { PropertiesDocContextSelector } from './PropertiesDocContextSelector';
import { PropertiesSection } from './PropertiesSection';
import './PropertiesView.scss';
import { DefaultStyleProvider } from './StyleProvider';
-import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
-import { KeyValueBox } from './nodes/KeyValueBox';
-import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
-import { SettingsManager } from '../util/SettingsManager';
const _global = (window /* browser */ || global) /* node */ as any;
interface PropertiesViewProps {
@@ -53,6 +52,12 @@ interface PropertiesViewProps {
export class PropertiesView extends React.Component<PropertiesViewProps> {
private _widthUndo?: UndoManager.Batch;
+ public static Instance: PropertiesView | undefined;
+ constructor(props: any) {
+ super(props);
+ PropertiesView.Instance = this;
+ }
+
@computed get MAX_EMBED_HEIGHT() {
return 200;
}
@@ -76,6 +81,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
@observable layoutFields: boolean = false;
+ @observable layoutDocAcls: boolean = false;
@observable openOptions: boolean = true;
@observable openSharing: boolean = true;
@@ -87,15 +93,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@observable openTransform: boolean = true;
@observable openFilters: boolean = false;
- /**
- * autorun to set up the filter doc of a collection if that collection has been selected and the filters panel is open
- */
- private selectedDocListenerDisposer: Opt<Lambda>;
-
- // @observable selectedUser: string = "";
- // @observable addButtonPressed: boolean = false;
- @observable layoutDocAcls: boolean = false;
-
//Pres Trails booleans:
@observable openPresTransitions: boolean = true;
@observable openPresProgressivize: boolean = false;
@@ -105,19 +102,29 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@observable inOptions: boolean = false;
@observable _controlButton: boolean = false;
+ private _disposers: { [name: string]: IReactionDisposer } = {};
componentDidMount() {
- this.selectedDocListenerDisposer?.();
- // this.selectedDocListenerDisposer = autorun(() => this.openFilters && this.selectedDoc && this.checkFilterDoc());
+ this._disposers.link = reaction(
+ () => LinkManager.currentLink,
+ link => {
+ link && this.CloseAll();
+ link && (this.openLinks = true);
+ },
+ { fireImmediately: true }
+ );
}
componentWillUnmount() {
- this.selectedDocListenerDisposer?.();
+ Object.values(this._disposers).forEach(disposer => disposer?.());
}
@computed get isInk() {
return this.selectedDoc?.type === DocumentType.INK;
}
+ @computed get isStack() {
+ return [CollectionViewType.Stacking, CollectionViewType.NoteTaking].includes(this.selectedDoc?.type_collection as any);
+ }
rtfWidth = () => (!this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[Width](), this.props.width - 20));
rtfHeight = () => (!this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[Width]() ? Math.min(this.selectedDoc?.[Height](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT);
@@ -364,7 +371,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<IconButton
icon={<FontAwesomeIcon icon={'ellipsis-h'} />}
size={Size.XSMALL}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
onClick={action(() => {
if (this.selectedDocumentView || this.selectedDoc) {
SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined, this.selectedDoc);
@@ -514,7 +521,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<div>
<br></br> Individuals with Access to this Document
</div>
- <div className="propertiesView-sharingTable" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }}>
+ <div className="propertiesView-sharingTable" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
{<div> {individualTableEntries}</div>}
</div>
{groupTableEntries.length > 0 ? (
@@ -522,7 +529,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<div>
<br></br> Groups with Access to this Document
</div>
- <div className="propertiesView-sharingTable" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }}>
+ <div className="propertiesView-sharingTable" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
{<div> {groupTableEntries}</div>}
</div>
</div>
@@ -542,27 +549,45 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
toggleCheckbox = () => (this.layoutFields = !this.layoutFields);
@computed get color() {
- return StrCast(Doc.UserDoc().userColor);
+ return SettingsManager.userColor;
}
@computed get backgroundColor() {
- return StrCast(Doc.UserDoc().userBackgroundColor);
+ return SettingsManager.userBackgroundColor;
}
@computed get variantColor() {
- return StrCast(Doc.UserDoc().userVariantColor);
+ return SettingsManager.userVariantColor;
}
@computed get editableTitle() {
const titles = new Set<string>();
- const title = Array.from(titles.keys()).length > 1 ? '--multiple selected--' : StrCast(this.selectedDoc?.title);
SelectionManager.Views().forEach(dv => titles.add(StrCast(dv.rootDoc.title)));
- return <EditableText val={title} setVal={this.setTitle} color={this.color} type={Type.SEC} formLabel={'Title'} fillWidth />;
+ const title = Array.from(titles.keys()).length > 1 ? '--multiple selected--' : StrCast(this.selectedDoc?.title);
+ return (
+ <div>
+ <EditableText val={title} setVal={this.setTitle} color={this.color} type={Type.SEC} formLabel={'Title'} fillWidth />
+ {LinkManager.currentLinkAnchor ? (
+ <p className="propertiesView-titleExtender">
+ <>
+ <b>Anchor:</b>
+ {LinkManager.currentLinkAnchor.title}
+ </>
+ </p>
+ ) : null}
+ {LinkManager.currentLink?.title ? (
+ <p className="propertiesView-titleExtender">
+ <>
+ <b>Link:</b>
+ {LinkManager.currentLink.title}
+ </>
+ </p>
+ ) : null}
+ </div>
+ );
}
@computed get currentType() {
- // console.log("current type " + this.selectedDoc?.type)
-
const documentType = StrCast(this.selectedDoc?.type);
var currentType: string = documentType;
var capitalizedDocType = Utils.cleanDocumentType(currentType as DocumentType);
@@ -586,9 +611,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
if (iconName) {
const Icon = Icons[iconName as keyof typeof Icons];
return <Icon />;
- } else {
- return <Icons.BsFillCollectionFill />;
}
+ return <Icons.BsFillCollectionFill />;
}
@undoBatch
@@ -759,39 +783,39 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
@computed get shapeXps() {
- return this.getField('x');
+ return NumCast(this.selectedDoc?.x);
}
@computed get shapeYps() {
- return this.getField('y');
+ return NumCast(this.selectedDoc?.y);
}
@computed get shapeHgt() {
- return this.getField('_height');
+ return NumCast(this.selectedDoc?._height);
}
@computed get shapeWid() {
- return this.getField('_width');
+ return NumCast(this.selectedDoc?._width);
}
set shapeXps(value) {
- this.selectedDoc && (this.selectedDoc.x = Number(value));
+ this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100);
}
set shapeYps(value) {
- this.selectedDoc && (this.selectedDoc.y = Number(value));
+ this.selectedDoc && (this.selectedDoc.y = Math.round(value * 100) / 100);
}
set shapeWid(value) {
- this.selectedDoc && (this.selectedDoc._width = Number(value));
+ this.selectedDoc && (this.selectedDoc._width = Math.round(value * 100) / 100);
}
set shapeHgt(value) {
- this.selectedDoc && (this.selectedDoc._height = Number(value));
+ this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100);
}
@computed get hgtInput() {
return this.inputBoxDuo(
'hgt',
this.shapeHgt,
- undoable((val: string) => !isNaN(Number(val)) && (this.shapeHgt = val), 'set height'),
+ undoable((val: string) => !isNaN(Number(val)) && (this.shapeHgt = +val), 'set height'),
'H:',
'wid',
this.shapeWid,
- undoable((val: string) => !isNaN(Number(val)) && (this.shapeWid = val), 'set width'),
+ undoable((val: string) => !isNaN(Number(val)) && (this.shapeWid = +val), 'set width'),
'W:'
);
}
@@ -799,11 +823,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
return this.inputBoxDuo(
'Xps',
this.shapeXps,
- undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeXps = val), 'set x coord'),
+ undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeXps = +val), 'set x coord'),
'X:',
'Yps',
this.shapeYps,
- undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeYps = val), 'set y coord'),
+ undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeYps = +val), 'set y coord'),
'Y:'
);
}
@@ -951,7 +975,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
};
@action
- onDoubleClick = () => {
+ CloseAll = () => {
this.openContexts = false;
this.openLinks = false;
this.openOptions = false;
@@ -1043,11 +1067,31 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
);
}
- getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any) => {
+ _sliderBatch: UndoManager.Batch | undefined;
+ setFinalNumber = () => {
+ this._sliderBatch?.end();
+ };
+ getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any, autorange?: number, autorangeMinVal?: number) => {
return (
- <div>
+ <div key={label + this.selectedDoc?.title}>
<NumberInput formLabel={label} formLabelPlacement={'left'} type={Type.SEC} unit={unit} fillWidth color={this.color} number={number} setNumber={setNumber} min={min} max={max} />
- <Slider multithumb={false} color={this.color} size={Size.XSMALL} min={min} max={max} unit={unit} number={number} setNumber={setNumber} fillWidth />
+ <Slider
+ key={label}
+ onPointerDown={e => (this._sliderBatch = UndoManager.StartBatch('slider ' + label))}
+ multithumb={false}
+ color={this.color}
+ size={Size.XSMALL}
+ min={min}
+ max={max}
+ autorangeMinVal={autorangeMinVal}
+ autorange={autorange}
+ number={number}
+ unit={unit}
+ decimals={1}
+ setFinalNumber={this.setFinalNumber}
+ setNumber={setNumber}
+ fillWidth
+ />
</div>
);
};
@@ -1055,81 +1099,43 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@computed get transformEditor() {
return (
<div className="transform-editor">
+ {!this.isStack ? null : this.getNumber('Gap', ' px', 0, 200, NumCast(this.selectedDoc!.gridGap), (val: number) => !isNaN(val) && (this.selectedDoc!.gridGap = val))}
+ {!this.isStack ? null : this.getNumber('xMargin', ' px', 0, 500, NumCast(this.selectedDoc!.xMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.xMargin = val))}
{this.isInk ? this.controlPointsButton : null}
- {this.getNumber(
- 'Height',
- ' px',
- 0,
- 1000,
- Number(this.shapeHgt),
- undoable((val: string) => !isNaN(Number(val)) && (this.shapeHgt = val), 'set height')
- )}
- {this.getNumber(
- 'Width',
- ' px',
- 0,
- 1000,
- Number(this.shapeWid),
- undoable((val: string) => !isNaN(Number(val)) && (this.shapeWid = val), 'set width')
- )}
- {this.getNumber(
- 'X', //'X Coordinate',
- ' px',
- -2000,
- 2000,
- Number(this.shapeXps),
- undoable((val: string) => !isNaN(Number(val)) && (this.shapeXps = val), 'set x coord')
- )}
- {this.getNumber(
- 'Y', //'Y Coordinate',
- ' px',
- -2000,
- 2000,
- Number(this.shapeYps),
- undoable((val: string) => !isNaN(Number(val)) && (this.shapeYps = val), 'set y coord')
- )}
+ {this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, (val: number) => !isNaN(val) && (this.shapeWid = val), 1000, 1)}
+ {this.getNumber('Height', ' px', 0, Math.max(1000, this.shapeHgt), this.shapeHgt, (val: number) => !isNaN(val) && (this.shapeHgt = val), 1000, 1)}
+ {this.getNumber('X', ' px', this.shapeXps - 500, this.shapeXps + 500, this.shapeXps, (val: number) => !isNaN(val) && (this.shapeXps = val), 1000)}
+ {this.getNumber('Y', ' px', this.shapeYps - 500, this.shapeYps + 500, this.shapeYps, (val: number) => !isNaN(val) && (this.shapeYps = val), 1000)}
</div>
);
}
@computed get optionsSubMenu() {
return (
- <PropertiesSection
- title="Options"
- content={<PropertiesButtons />}
- inSection={this.inOptions}
- isOpen={this.openOptions}
- setInSection={bool => (this.inOptions = bool)}
- setIsOpen={bool => (this.openOptions = bool)}
- onDoubleClick={() => this.onDoubleClick()}
- />
+ <PropertiesSection title="Options" inSection={this.inOptions} isOpen={this.openOptions} setInSection={bool => (this.inOptions = bool)} setIsOpen={bool => (this.openOptions = bool)} onDoubleClick={this.CloseAll}>
+ <PropertiesButtons />
+ </PropertiesSection>
);
}
@computed get sharingSubMenu() {
return (
- <PropertiesSection
- title="Sharing & Permissions"
- content={
- <>
- {/* <div className="propertiesView-buttonContainer"> */}
- <div className="propertiesView-acls-checkbox">
- Layout Permissions
- <Checkbox color="primary" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} />
- </div>
- {/* <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}>
- <button onPointerDown={() => SharingManager.Instance.distributeOverCollection(this.selectedDoc!)}>
- <FontAwesomeIcon icon="redo-alt" size="1x" />
- </button>
+ <PropertiesSection title="Sharing and Permissions" isOpen={this.openSharing} setIsOpen={bool => (this.openSharing = bool)} onDoubleClick={() => this.CloseAll()}>
+ <>
+ {/* <div className="propertiesView-buttonContainer"> */}
+ <div className="propertiesView-acls-checkbox">
+ Layout Permissions
+ <Checkbox color="primary" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} />
+ </div>
+ {/* <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}>
+ <button onPointerDown={() => SharingManager.Instance.distributeOverCollection(this.selectedDoc!)}>
+ <FontAwesomeIcon icon="redo-alt" size="1x" />
+ </button>
</Tooltip> */}
- {/* </div> */}
- {this.sharingTable}
- </>
- }
- isOpen={this.openSharing}
- setIsOpen={bool => (this.openSharing = bool)}
- onDoubleClick={() => this.onDoubleClick()}
- />
+ {/* </div> */}
+ {this.sharingTable}
+ </>
+ </PropertiesSection>
);
}
@@ -1159,17 +1165,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@computed get filtersSubMenu() {
return (
- <PropertiesSection
- title="Filters"
- content={
- <div className="propertiesView-content filters" style={{ position: 'relative', height: 'auto' }}>
- <FilterPanel rootDoc={this.selectedDoc ?? Doc.ActiveDashboard!} />
- </div>
- }
- isOpen={this.openFilters}
- setIsOpen={bool => (this.openFilters = bool)}
- onDoubleClick={() => this.onDoubleClick()}
- />
+ <PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={bool => (this.openFilters = bool)} onDoubleClick={() => this.CloseAll()}>
+ <div className="propertiesView-content filters" style={{ position: 'relative', height: 'auto' }}>
+ <FilterPanel rootDoc={this.selectedDoc ?? Doc.ActiveDashboard!} />
+ </div>
+ </PropertiesSection>
);
}
@@ -1178,42 +1178,46 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
return (
<>
- <PropertiesSection title="Appearance" content={this.isInk ? this.appearanceEditor : null} isOpen={this.openAppearance} setIsOpen={bool => (this.openAppearance = bool)} onDoubleClick={() => this.onDoubleClick()} />
- <PropertiesSection title="Transform" content={this.transformEditor} isOpen={this.openTransform} setIsOpen={bool => (this.openTransform = bool)} onDoubleClick={() => this.onDoubleClick()} />
+ <PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => (this.openAppearance = bool)} onDoubleClick={() => this.CloseAll()}>
+ {this.isInk ? this.appearanceEditor : null}
+ </PropertiesSection>
+ <PropertiesSection title="Transform" isOpen={this.openTransform} setIsOpen={bool => (this.openTransform = bool)} onDoubleClick={() => this.CloseAll()}>
+ {this.transformEditor}
+ </PropertiesSection>
</>
);
}
@computed get fieldsSubMenu() {
return (
- <PropertiesSection
- title="Fields & Tags"
- content={<div className="propertiesView-content fields">{Doc.noviceMode ? this.noviceFields : this.expandedField}</div>}
- isOpen={this.openFields}
- setIsOpen={bool => (this.openFields = bool)}
- onDoubleClick={() => this.onDoubleClick()}
- />
+ <PropertiesSection title="Fields & Tags" isOpen={this.openFields} setIsOpen={bool => (this.openFields = bool)} onDoubleClick={() => this.CloseAll()}>
+ <div className="propertiesView-content fields">{Doc.noviceMode ? this.noviceFields : this.expandedField}</div>
+ </PropertiesSection>
);
}
@computed get contextsSubMenu() {
return (
- <PropertiesSection
- title="Other Contexts"
- content={this.contextCount > 0 ? this.contexts : 'There are no other contexts.'}
- isOpen={this.openContexts}
- setIsOpen={bool => (this.openContexts = bool)}
- onDoubleClick={() => this.onDoubleClick()}
- />
+ <PropertiesSection title="Other Contexts" isOpen={this.openContexts} setIsOpen={bool => (this.openContexts = bool)} onDoubleClick={() => this.CloseAll()}>
+ {this.contextCount > 0 ? this.contexts : 'There are no other contexts.'}
+ </PropertiesSection>
);
}
@computed get linksSubMenu() {
- return <PropertiesSection title="Linked To" content={this.linkCount > 0 ? this.links : 'There are no current links.'} isOpen={this.openLinks} setIsOpen={bool => (this.openLinks = bool)} onDoubleClick={() => this.onDoubleClick()} />;
+ return (
+ <PropertiesSection title="Linked To" isOpen={this.openLinks} setIsOpen={bool => (this.openLinks = bool)} onDoubleClick={this.CloseAll}>
+ {this.linkCount > 0 ? this.links : 'There are no current links.'}
+ </PropertiesSection>
+ );
}
@computed get layoutSubMenu() {
- return <PropertiesSection title="Layout" content={this.layoutPreview} isOpen={this.openLayout} setIsOpen={bool => (this.openLayout = bool)} onDoubleClick={() => this.onDoubleClick()} />;
+ return (
+ <PropertiesSection title="Layout" isOpen={this.openLayout} setIsOpen={bool => (this.openLayout = bool)} onDoubleClick={this.CloseAll}>
+ {this.layoutPreview}
+ </PropertiesSection>
+ );
}
@computed get description() {
@@ -1323,10 +1327,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
};
onDescriptionKey = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
- if (e.key === 'Enter') {
- this.setDescripValue(this.description);
- document.getElementById('link_description_input')?.blur();
- }
+ // if (e.key === 'Enter') {
+ // this.setDescripValue(this.description);
+ // document.getElementById('link_description_input')?.blur();
+ // }
};
onSelectOutRelationship = () => {
@@ -1539,6 +1543,16 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</button>
</div>
<div className="propertiesView-input inline">
+ <p>Play Target Video</p>
+ <button
+ style={{ background: !this.sourceAnchor?.followLinkVideo ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkVideo', this.sourceAnchor)}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline">
<p>Zoom Text Selections</p>
<button
style={{ background: !this.sourceAnchor?.followLinkZoomText ? '' : '#4476f7', borderRadius: 3 }}
@@ -1671,8 +1685,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<div
className="propertiesView"
style={{
- background: StrCast(Doc.UserDoc().userBackgroundColor),
- color: StrCast(Doc.UserDoc().userColor),
+ background: SettingsManager.userBackgroundColor,
+ color: SettingsManager.userColor,
width: this.props.width,
minWidth: this.props.width,
}}>
@@ -1689,12 +1703,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<div className="propertiesView-type"> {this.currentType} </div>
{this.optionsSubMenu}
{this.linksSubMenu}
- {!this.selectedDoc || !LinkManager.currentLink || (!hasSelectedAnchor && this.selectedDoc !== LinkManager.currentLink) ? null : this.linkProperties}
+ {!LinkManager.currentLink || !this.openLinks ? null : this.linkProperties}
{this.inkSubMenu}
{this.contextsSubMenu}
{this.fieldsSubMenu}
{isNovice ? null : this.sharingSubMenu}
- {isNovice ? null : this.filtersSubMenu}
+ {this.filtersSubMenu}
{isNovice ? null : this.layoutSubMenu}
</div>
);
@@ -1742,12 +1756,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
color: SettingsManager.userColor,
backgroundColor: this.openPresVisibilityAndDuration ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
}}>
- &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Visibilty
+ &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Visibility
<div className="propertiesView-presentationTrails-title-icon">
<FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" />
</div>
</div>
- {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibiltyDurationDropdown}</div> : null}
+ {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibilityDurationDropdown}</div> : null}
</div>
)}
{!selectedItem ? null : (
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 2bc2d5e6b..9966dbced 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -5,6 +5,7 @@ import * as React from 'react';
import { DocumentManager } from '../util/DocumentManager';
import { CompileScript, Transformer, ts } from '../util/Scripting';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { SettingsManager } from '../util/SettingsManager';
import { undoable } from '../util/UndoManager';
import { DocumentIconContainer } from './nodes/DocumentIcon';
import { OverlayView } from './OverlayView';
@@ -245,17 +246,29 @@ export class ScriptingRepl extends React.Component {
render() {
return (
<div className="scriptingRepl-outerContainer">
- <div className="scriptingRepl-commandsContainer" ref={this.commandsRef}>
+ <div className="scriptingRepl-commandsContainer" style={{ background: SettingsManager.userBackgroundColor }} ref={this.commandsRef}>
{this.commands.map(({ command, result }, i) => {
return (
- <div className="scriptingRepl-resultContainer" key={i}>
- <div className="scriptingRepl-commandString">{command || <br />}</div>
- <div className="scriptingRepl-commandResult">{<ScriptingValueDisplay scrollToBottom={this.maybeScrollToBottom} value={result} />}</div>
+ <div className="scriptingRepl-resultContainer" style={{ background: SettingsManager.userBackgroundColor }} key={i}>
+ <div className="scriptingRepl-commandString" style={{ background: SettingsManager.userBackgroundColor }}>
+ {command || <br />}
+ </div>
+ <div className="scriptingRepl-commandResult" style={{ background: SettingsManager.userBackgroundColor }}>
+ {<ScriptingValueDisplay scrollToBottom={this.maybeScrollToBottom} value={result} />}
+ </div>
</div>
);
})}
</div>
- <input className="scriptingRepl-commandInput" onFocus={this.onFocus} onBlur={this.onBlur} value={this.commandString} onChange={this.onChange} onKeyDown={this.onKeyDown}></input>
+ <input
+ className="scriptingRepl-commandInput"
+ style={{ background: SettingsManager.userBackgroundColor }} //
+ onFocus={this.onFocus}
+ onBlur={this.onBlur}
+ value={this.commandString}
+ onChange={this.onChange}
+ onKeyDown={this.onKeyDown}
+ />
</div>
);
}
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index f3452c780..1e1b8e0e6 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -62,12 +62,9 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author)));
return Array.from(keys.keys()).sort();
}
- get filtersKey() {
- return '_' + this.sidebarKey + '_childFilters';
- }
anchorMenuClick = (anchor: Doc, filterExlusions?: string[]) => {
- const startup = StrListCast(this.props.rootDoc.childFilters)
+ const startup = this.childFilters()
.map(filter => filter.split(':')[0])
.join(' ');
const target = Docs.Create.TextDocument(startup, {
@@ -79,7 +76,6 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
_layout_autoHeight: true,
_text_fontSize: StrCast(Doc.UserDoc().fontSize),
_text_fontFamily: StrCast(Doc.UserDoc().fontFamily),
- target: 'HELLO' as any,
});
FormattedTextBox.SelectOnLoad = target[Id];
FormattedTextBox.DontSelectInitialText = true;
@@ -152,8 +148,9 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
};
makeDocUnfiltered = (doc: Doc) => {
if (DocListCast(this.props.rootDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
- if (this.props.layoutDoc[this.filtersKey]) {
- this.props.layoutDoc[this.filtersKey] = new List<string>();
+ if (this.childFilters()) {
+ // if any child filters exist, get rid of them
+ this.props.layoutDoc._childFilters = new List<string>();
}
return true;
}
@@ -179,7 +176,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey);
moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey);
- childFilters = () => [...StrListCast(this.props.layoutDoc._childFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])];
+ childFilters = () => StrListCast(this.props.layoutDoc._childFilters);
layout_showTitle = () => 'title';
setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight());
sortByLinkAnchorY = (a: Doc, b: Doc) => {
@@ -189,25 +186,25 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
};
render() {
const renderTag = (tag: string) => {
- const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`tags::${tag}::check`);
+ const active = this.childFilters().includes(`tags${Doc.FilterSep}${tag}${Doc.FilterSep}check`);
return (
- <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, 'tags', tag, 'check', true, this.sidebarKey, e.shiftKey)}>
+ <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, 'tags', tag, 'check', true, undefined, e.shiftKey)}>
{tag}
</div>
);
};
const renderMeta = (tag: string, dflt: FieldResult<Field>) => {
- const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${dflt}:exists`);
+ const active = this.childFilters().includes(`${tag}${Doc.FilterSep}${Doc.FilterAny}${Doc.FilterSep}exists`);
return (
- <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, tag, dflt, 'exists', true, this.sidebarKey, e.shiftKey)}>
+ <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}>
{tag}
</div>
);
};
const renderUsers = (user: string) => {
- const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`author:${user}:check`);
+ const active = this.childFilters().includes(`author:${user}:check`);
return (
- <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, 'author', user, 'check', true, this.sidebarKey, e.shiftKey)}>
+ <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, 'author', user, 'check', true, undefined, e.shiftKey)}>
{user}
</div>
);
@@ -225,11 +222,11 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
height: '100%',
}}>
<div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight() }} onWheel={e => e.stopPropagation()}>
- {this.allUsers.map(renderUsers)}
+ {this.allUsers.length > 1 ? this.allUsers.map(renderUsers) : null}
{this.allHashtags.map(renderTag)}
- {/* {Array.from(this.allMetadata.keys())
+ {Array.from(this.allMetadata.keys())
.sort()
- .map(key => renderMeta(key, this.allMetadata.get(key)))} */}
+ .map(key => renderMeta(key, this.allMetadata.get(key)))}
</div>
<div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
index c06bb287e..f069e7e1b 100644
--- a/src/client/views/StyleProvider.scss
+++ b/src/client/views/StyleProvider.scss
@@ -18,10 +18,15 @@
cursor: default;
}
.styleProvider-filter {
- right: 0;
+ right: 15;
+ .styleProvider-filterShift {
+ left: 0;
+ top: 0;
+ position: absolute;
+ }
}
.styleProvider-audio {
- right: 15;
+ right: 30;
}
.styleProvider-lock:hover,
.styleProvider-audio:hover,
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 12935400b..f2e1ee0a2 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -1,7 +1,7 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
-import { IconButton, Shadows, Size } from 'browndash-components';
+import { Dropdown, DropdownType, IconButton, IListItemProps, ListBox, ListItem, Popup, Shadows, Size, Type } from 'browndash-components';
import { action, runInAction } from 'mobx';
import { extname } from 'path';
import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
@@ -21,9 +21,10 @@ import { InkingStroke } from './InkingStroke';
import { DocumentView, DocumentViewProps } from './nodes/DocumentView';
import { FieldViewProps } from './nodes/FieldView';
import { KeyValueBox } from './nodes/KeyValueBox';
-import { SliderBox } from './nodes/SliderBox';
import './StyleProvider.scss';
import React = require('react');
+import { PropertiesView } from './PropertiesView';
+import { FaFilter } from 'react-icons/fa';
export enum StyleProp {
TreeViewIcon = 'treeView_Icon',
@@ -50,10 +51,6 @@ export enum StyleProp {
Highlighting = 'highlighting', // border highlighting
}
-function darkScheme() {
- return Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark;
-}
-
function toggleLockedPosition(doc: Doc) {
UndoManager.RunInBatch(
() =>
@@ -88,12 +85,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
const isCaption = property.includes(':caption');
const isAnchor = property.includes(':anchor');
const isAnnotated = property.includes(':annotated');
+ const isInk = () => doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name) && !props?.LayoutTemplateString;
const isOpen = property.includes(':open');
const isEmpty = property.includes(':empty');
const boxBackground = property.includes(':box');
const fieldKey = props?.fieldKey ? props.fieldKey + '_' : isCaption ? 'caption_' : '';
const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
+ const color = () => props?.styleProvider?.(doc, props, StyleProp.Color);
const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
const layout_showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
// prettier-ignore
@@ -118,7 +117,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) {
const selected = SelectionManager.Views().some(dv => dv.rootDoc === doc);
const highlightIndex = Doc.isBrushedHighlightedDegree(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
- const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? 'black' : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
+ const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? "black" : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
if (highlightIndex) {
return {
@@ -131,7 +130,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
}
return undefined;
case StyleProp.DocContents:return undefined;
- case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : darkScheme() ? 'lightgrey' : 'dimgrey';
+ case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
case StyleProp.Opacity: return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
case StyleProp.HideLinkBtn:return props?.hideLinkButton || (!selected && doc?.layout_hideLinkButton);
case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(doc?._text_fontSize, StrCast(Doc.UserDoc().fontSize)));
@@ -143,6 +142,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
case StyleProp.ShowTitle:
return (
(doc &&
+ !props?.LayoutTemplateString &&
!doc.presentation_targetDoc &&
!props?.LayoutTemplateString?.includes(KeyValueBox.name) &&
props?.layout_showTitle?.() !== '' &&
@@ -159,8 +159,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
);
case StyleProp.Color:
if (DocumentView.LastPressedSidebarBtn === doc) return SettingsManager.userBackgroundColor;
- if (Doc.IsSystem(doc!)) return StrCast(Doc.UserDoc().userColor)
- if (doc?.type === DocumentType.FONTICON) return Doc.UserDoc().userColor;
+ if (Doc.IsSystem(doc!)) return SettingsManager.userColor;
+ if (doc?.type === DocumentType.FONTICON) return SettingsManager.userColor;
const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
if (docColor) return docColor;
const docView = props?.DocumentView?.();
@@ -193,28 +193,26 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
? 15
: 0;
case StyleProp.BackgroundColor: {
- if (DocumentView.LastPressedSidebarBtn === doc) return StrCast(Doc.UserDoc().userColor); // hack to indicate active menu panel item
+ if (DocumentView.LastPressedSidebarBtn === doc) return SettingsManager.userColor; // hack to indicate active menu panel item
let docColor: Opt<string> = StrCast(doc?.[fieldKey + '_backgroundColor'], StrCast(doc?._backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
// prettier-ignore
switch (doc?.type) {
- case DocumentType.SLIDER: break;
- case DocumentType.PRESELEMENT: docColor = docColor || (darkScheme() ? '' : ''); break;
- case DocumentType.PRES: docColor = docColor || (darkScheme() ? 'transparent' : 'transparent'); break;
+ case DocumentType.PRESELEMENT: docColor = docColor || ""; break;
+ case DocumentType.PRES: docColor = docColor || 'transparent'; break;
case DocumentType.FONTICON: docColor = boxBackground ? undefined : docColor || Colors.DARK_GRAY; break;
- case DocumentType.RTF: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break;
+ case DocumentType.RTF: docColor = docColor || Colors.LIGHT_GRAY; break;
case DocumentType.INK: docColor = doc?.stroke_isInkMask ? 'rgba(0,0,0,0.7)' : undefined; break;
case DocumentType.EQUATION: docColor = docColor || 'transparent'; break;
- case DocumentType.LABEL: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break;
- case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break;
+ case DocumentType.LABEL: docColor = docColor || Colors.LIGHT_GRAY; break;
+ case DocumentType.BUTTON: docColor = docColor || Colors.LIGHT_GRAY; break;
case DocumentType.LINK: docColor = (isAnchor ? docColor : '') || 'transparent'; break;
case DocumentType.IMG:
case DocumentType.WEB:
case DocumentType.PDF:
case DocumentType.MAP:
case DocumentType.SCREENSHOT:
- case DocumentType.VID: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break;
+ case DocumentType.VID: docColor = docColor || (Colors.LIGHT_GRAY); break;
case DocumentType.COL:
- if (StrCast(Doc.LayoutField(doc)).includes(SliderBox.name)) break;
docColor = docColor || (Doc.IsSystem(doc)
? SettingsManager.userBackgroundColor
: doc.annotationOn
@@ -222,11 +220,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
: doc?._isGroup
? undefined
: doc._type_collection === CollectionViewType.Stacking ?
- (darkScheme() ? Colors.MEDIUM_GRAY : Colors.DARK_GRAY)
- : Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (darkScheme() ? Colors.BLACK : Colors.MEDIUM_GRAY));
+ (Colors.DARK_GRAY)
+ : Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY));
break;
//if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)";
- default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE);
+ default: docColor = docColor || (Colors.WHITE);
}
return (docColor && !doc) ? DashColor(docColor).fade(0.5).toString() : docColor;
}
@@ -237,12 +235,12 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
switch (doc?.type) {
case DocumentType.COL:
return StrCast(
- doc?.layout_borderRounding,
+ doc?.layout_boxShadow,
doc?._type_collection === CollectionViewType.Pile
? '4px 4px 10px 2px'
: lockedPosition() || doc?._isGroup || docProps?.LayoutTemplateString
? undefined // groups have no drop shadow -- they're supposed to be "invisible". LayoutString's imply collection is being rendered as something else (e.g., title of a Slide)
- : `${darkScheme() ? Colors.DARK_GRAY : Colors.MEDIUM_GRAY} ${StrCast(doc.layout_borderRounding, '0.2vw 0.2vw 0.8vw')}`
+ : `${Colors.MEDIUM_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
);
case DocumentType.LABEL:
@@ -260,14 +258,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
}
}
case StyleProp.PointerEvents:
- const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name) && !props?.LayoutTemplateString;
if (StrCast(doc?.pointerEvents) && !props?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
if (docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.() !== undefined) return docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.();
- if (DocumentView.ExploreMode || doc?.layout_unrendered) return isInk ? 'visiblePainted' : 'all';
+ if (DocumentView.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
if (props?.contentPointerEvents) return StrCast(props.contentPointerEvents);
if (props?.pointerEvents?.() === 'none') return 'none';
if (opacity() === 0) return 'none';
- if (props?.isDocumentActive?.()) return isInk ? 'visiblePainted' : 'all';
+ if (props?.isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all';
return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
case StyleProp.Decorations:
const lock = () => {
@@ -280,22 +277,56 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
}
};
const filter = () => {
+ const dashView = DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard);
const showFilterIcon =
StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
- ? '#18c718bd' //'hasFilter'
+ ? 'green' // #18c718bd' //'hasFilter'
: docProps?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragsDocFilter).length || docProps?.childFiltersByRanges().length
? 'orange' //'inheritsFilter'
: undefined;
return !showFilterIcon ? null : (
- <div className="styleProvider-filter" onClick={action(() => (SettingsManager.propertiesWidth = 250))}>
- <FontAwesomeIcon icon={'filter'} size="lg" style={{ position: 'absolute', top: '1%', right: '1%', cursor: 'pointer', padding: 1, color: showFilterIcon, zIndex: 1 }} />
+ <div className="styleProvider-filter">
+ <Dropdown
+ type={Type.TERT}
+ dropdownType={DropdownType.CLICK}
+ fillWidth
+ iconProvider={(active:boolean) => <div className='styleProvider-filterShift'><FaFilter/></div>}
+ closeOnSelect={true}
+ setSelectedVal={
+ action((dv) => {
+ (dv as any).select(false);
+ (SettingsManager.propertiesWidth = 250);
+ setTimeout(action(() => {
+ if (PropertiesView.Instance) {
+ PropertiesView.Instance.CloseAll();
+ PropertiesView.Instance.openFilters = true;
+ }
+ }));
+ })
+ }
+ size={Size.XSMALL}
+ width={15}
+ height={15}
+ title={showFilterIcon === 'green' ?
+ "This view is filtered. Click to view/change filters":
+ "this view inherits filters from one of its parents"}
+ color={SettingsManager.userColor}
+ background={showFilterIcon}
+ items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[]), ...(props?.DocumentView?[props?.DocumentView?.()]:[])]
+ .filter(dv => StrListCast(dv.rootDoc.childFilters).length || StrListCast(dv.rootDoc.childRangeFilters).length)
+ .map(dv => ({
+ text: StrCast(dv.rootDoc.title),
+ val: dv as any,
+ style: {color:SettingsManager.userColor, background:SettingsManager.userBackgroundColor},
+ } as IListItemProps)) }
+ />
</div>
);
};
const audio = () => {
const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, 'stopped');
const audioAnnosCount = (doc: Doc) => StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations']).length;
- if (!doc || props?.renderDepth === -1 || (!audioAnnosCount(doc) && audioAnnoState(doc) === 'stopped')) return null;
+ if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null;
const audioIconColors: { [key: string]: string } = { recording: 'red', playing: 'green', stopped: 'blue' };
return (
<Tooltip title={<div>{StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations_text']).lastElement()}</div>}>
@@ -316,7 +347,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
}
export function DashboardToggleButton(doc: Doc, field: string, onIcon: IconProp, offIcon: IconProp, clickFunc?: () => void) {
- const color = StrCast(Doc.UserDoc().userColor);
+ const color = SettingsManager.userColor;
return (
<IconButton
size={Size.XSMALL}
diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx
index 4f971742b..f07e38af1 100644
--- a/src/client/views/UndoStack.tsx
+++ b/src/client/views/UndoStack.tsx
@@ -1,13 +1,12 @@
-import { action, observable } from 'mobx';
+import { Tooltip } from '@mui/material';
+import { Popup, Type } from 'browndash-components';
+import { observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { UndoManager } from '../util/UndoManager';
-import './UndoStack.scss';
import { StrCast } from '../../fields/Types';
-import { Doc } from '../../fields/Doc';
-import { Popup, Type, isDark } from 'browndash-components';
-import { Colors } from './global/globalEnums';
import { SettingsManager } from '../util/SettingsManager';
+import { UndoManager } from '../util/UndoManager';
+import './UndoStack.scss';
interface UndoStackProps {
width?: number;
@@ -22,39 +21,48 @@ export class UndoStack extends React.Component<UndoStackProps> {
const background = UndoManager.batchCounter.get() ? 'yellow' : SettingsManager.userVariantColor;
const color = UndoManager.batchCounter.get() ? 'black' : SettingsManager.userColor;
return this.props.inline && UndoStack.HideInline ? null : (
- <div className="undoStack-outerContainer">
- <Popup
- text={'Undo/Redo Stack'}
- color={color}
- background={background}
- placement={`top-start`}
- type={Type.TERT}
- popup={
- <div
- className="undoStack-commandsContainer"
- ref={r => r?.scroll({ behavior: 'auto', top: r?.scrollHeight + 20 })}
- style={{
- background,
- color,
- }}>
- {UndoManager.undoStackNames.map((name, i) => (
- <div className="undoStack-resultContainer" key={i}>
- <div className="undoStack-commandString">{name.replace(/[^\.]*\./, '')}</div>
- </div>
- ))}
- {Array.from(UndoManager.redoStackNames)
- .reverse()
- .map((name, i) => (
- <div className="undoStack-resultContainer" key={i}>
- <div className="undoStack-commandString" style={{ fontWeight: 'bold', color: SettingsManager.userBackgroundColor }}>
- {name.replace(/[^\.]*\./, '')}
+ <Tooltip title={'undo stack (if it stays yellow, undo is broken - you should reload Dash)'}>
+ <div>
+ <div className="undoStack-outerContainer">
+ <Popup
+ text="stack"
+ color={color}
+ background={background}
+ placement={`top-start`}
+ type={Type.TERT}
+ popup={
+ <div
+ className="undoStack-commandsContainer"
+ ref={r => r?.scroll({ behavior: 'auto', top: r?.scrollHeight + 20 })}
+ style={{
+ background,
+ color,
+ }}>
+ {Array.from(UndoManager.undoStackNames).map((name, i) => (
+ <div className="undoStack-resultContainer" key={i}
+ onClick={e => {
+ const size = UndoManager.undoStackNames.length;
+ for (let n = 0; n < size-i; n++ ) UndoManager.Undo(); } }
+ >
+ <div className="undoStack-commandString">{StrCast(name).replace(/[^\.]*\./, '')}</div>
</div>
- </div>
- ))}
- </div>
- }
- />
- </div>
+ ))}
+ {Array.from(UndoManager.redoStackNames)
+ .reverse()
+ .map((name, i) => (
+ <div className="undoStack-resultContainer" key={i} onClick={e =>
+ { for (let n = 0; n <= i; n++ ) UndoManager.Redo() }}>
+ <div className="undoStack-commandString" style={{ fontWeight: 'bold', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
+ {StrCast(name).replace(/[^\.]*\./, '')}
+ </div>
+ </div>
+ ))}
+ </div>
+ }
+ />
+ </div>
+ </div>
+ </Tooltip>
);
}
}
diff --git a/src/client/views/collections/CollectionCarousel3DView.scss b/src/client/views/collections/CollectionCarousel3DView.scss
index 6bd1d9f5f..8319f19ca 100644
--- a/src/client/views/collections/CollectionCarousel3DView.scss
+++ b/src/client/views/collections/CollectionCarousel3DView.scss
@@ -74,7 +74,10 @@
.carousel3DView-fwd,
.carousel3DView-back {
- top: 50%;
+ top: 0;
+ background: transparent;
+ width: calc((1 - #{$CAROUSEL3D_CENTER_SCALE} * 0.33) / 2 * 100%);
+ height: 100%;
}
.carousel3DView-fwd-scroll,
@@ -108,8 +111,6 @@
opacity: 1;
}
-.carousel3DView-back:hover,
-.carousel3DView-fwd:hover,
.carousel3DView-back-scroll:hover,
.carousel3DView-fwd-scroll:hover {
background: lightgray;
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index d94e552b4..a8d080953 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -2,14 +2,15 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc } from '../../../fields/Doc';
+import { Doc, DocListCast } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
-import { NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { returnFalse, returnZero, Utils } from '../../../Utils';
+import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager } from '../../util/DragManager';
import { SelectionManager } from '../../util/SelectionManager';
import { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } from '../global/globalCssVariables.scss';
-import { DocumentView } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
import { StyleProp } from '../StyleProvider';
import './CollectionCarousel3DView.scss';
import { CollectionSubView } from './CollectionSubView';
@@ -27,7 +28,6 @@ export class CollectionCarousel3DView extends CollectionSubView() {
}
protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
- //used for stacking and masonry view
this._dropDisposer?.();
if (ele) {
this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
@@ -46,6 +46,15 @@ export class CollectionCarousel3DView extends CollectionSubView() {
.translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this.props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2)
.scale(1 / this.centerScale);
+ focus = (anchor: Doc, options: DocFocusOptions) => {
+ const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
+ if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return;
+ options.didMove = true;
+ const target = DocCast(anchor.annotationOn) ?? anchor;
+ const index = docs.indexOf(target);
+ index !== -1 && (this.layoutDoc._carousel_index = index);
+ return undefined;
+ };
@computed get content() {
const currentIndex = NumCast(this.layoutDoc._carousel_index);
const displayDoc = (childPair: { layout: Doc; data: Doc }) => {
@@ -61,6 +70,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
LayoutTemplateString={this.props.childLayoutString}
Document={childPair.layout}
DataDoc={childPair.data}
+ focus={this.focus}
ScreenToLocalTransform={this.childScreenToLocal}
isContentActive={this.isChildContentActive}
isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
@@ -85,8 +95,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length;
};
- onArrowClick = (e: React.MouseEvent, direction: number) => {
- e.stopPropagation();
+ onArrowClick = (direction: number) => {
this.changeSlide(direction);
!this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = direction === 1 ? 'fwd' : 'back'); // while autoscroll is on, keep the other autoscroll button hidden
!this.layoutDoc.autoScrollOn && this.fadeScrollButton(); // keep pause button visible while autoscroll is on
@@ -117,16 +126,11 @@ export class CollectionCarousel3DView extends CollectionSubView() {
};
@computed get buttons() {
- if (!this.props.isContentActive()) return null;
return (
<div className="arrow-buttons">
- <div key="back" className="carousel3DView-back" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={e => this.onArrowClick(e, -1)}>
- <FontAwesomeIcon icon="angle-left" size={'2x'} />
- </div>
- <div key="fwd" className="carousel3DView-fwd" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={e => this.onArrowClick(e, 1)}>
- <FontAwesomeIcon icon="angle-right" size={'2x'} />
- </div>
- {this.autoScrollButton}
+ <div title="click to go back" key="back" className="carousel3DView-back" onClick={e => this.onArrowClick(-1)} />
+ <div title="click to advance" key="fwd" className="carousel3DView-fwd" onClick={e => this.onArrowClick(1)} />
+ {/* {this.autoScrollButton} */}
</div>
);
}
@@ -165,7 +169,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
<div className="carousel-wrapper" style={{ transform: `translateX(${this.translateX}px)` }}>
{this.content}
</div>
- {this.props.Document._chromeHidden ? null : this.buttons}
+ {this.buttons}
<div className="dot-bar">{this.dots}</div>
</div>
);
diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss
index 8660113cd..130b31325 100644
--- a/src/client/views/collections/CollectionCarouselView.scss
+++ b/src/client/views/collections/CollectionCarouselView.scss
@@ -1,8 +1,7 @@
-
.collectionCarouselView-outer {
- height : 100%;
+ height: 100%;
.collectionCarouselView-caption {
- height: 50;
+ height: 50;
display: inline-block;
width: 100%;
}
@@ -13,7 +12,8 @@
user-select: none;
}
}
-.carouselView-back, .carouselView-fwd {
+.carouselView-back,
+.carouselView-fwd {
position: absolute;
display: flex;
top: 42.5%;
@@ -22,18 +22,19 @@
align-items: center;
border-radius: 5px;
justify-content: center;
- color: rgba(255,255,255,0.5);
- background : rgba(0,0,0, 0.1);
+ color: rgba(255, 255, 255, 0.5);
+ background: rgba(0, 0, 0, 0.1);
&:hover {
- color:white;
+ color: white;
}
}
.carouselView-fwd {
- right: 0;
+ right: 20;
}
.carouselView-back {
- left: 0;
+ left: 20;
}
-.carouselView-back:hover, .carouselView-fwd:hover {
+.carouselView-back:hover,
+.carouselView-fwd:hover {
background: lightgray;
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index ea02bcd4c..33a92d406 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Opt } from '../../../fields/Doc';
import { NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnFalse, returnZero, StopEvent } from '../../../Utils';
+import { emptyFunction, returnFalse, returnOne, returnZero, StopEvent } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
@@ -21,7 +21,6 @@ export class CollectionCarouselView extends CollectionSubView() {
}
protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
- //used for stacking and masonry view
this._dropDisposer?.();
if (ele) {
this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
@@ -44,12 +43,14 @@ export class CollectionCarouselView extends CollectionSubView() {
panelHeight = () => this.props.PanelHeight() - (StrCast(this.layoutDoc._layout_showCaption) ? 50 : 0);
onContentDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
onContentClick = () => ScriptCast(this.layoutDoc.onChildClick);
+ @computed get marginX() {
+ return NumCast(this.layoutDoc.caption_xMargin, 50);
+ }
+ captionWidth = () => this.props.PanelWidth() - 2 * this.marginX;
@computed get content() {
const index = NumCast(this.layoutDoc._carousel_index);
const curDoc = this.childLayoutPairs?.[index];
- const captionProps = { ...this.props, fieldKey: 'caption', setHeight: undefined };
- const marginX = NumCast(this.layoutDoc['caption_xMargin']);
- const marginY = NumCast(this.layoutDoc['caption_yMargin']);
+ const captionProps = { ...this.props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined };
const show_captions = StrCast(this.layoutDoc._layout_showCaption);
return !(curDoc?.layout instanceof Doc) ? null : (
<>
@@ -58,6 +59,7 @@ export class CollectionCarouselView extends CollectionSubView() {
{...this.props}
NativeWidth={returnZero}
NativeHeight={returnZero}
+ setContentView={undefined}
onDoubleClick={this.onContentDoubleClick}
onClick={this.onContentClick}
isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.props.isContentActive}
@@ -79,11 +81,11 @@ export class CollectionCarouselView extends CollectionSubView() {
style={{
display: show_captions ? undefined : 'none',
borderRadius: this.props.styleProvider?.(this.layoutDoc, captionProps, StyleProp.BorderRounding),
- marginRight: marginX,
- marginLeft: marginX,
- width: `calc(100% - ${marginX * 2}px)`,
+ marginRight: this.marginX,
+ marginLeft: this.marginX,
+ width: `calc(100% - ${this.marginX * 2}px)`,
}}>
- <FormattedTextBox key={index} {...captionProps} allowScroll={true} fieldKey={show_captions} styleProvider={this.captionStyleProvider} Document={curDoc.layout} DataDoc={undefined} />
+ <FormattedTextBox key={index} {...captionProps} fieldKey={show_captions} styleProvider={this.captionStyleProvider} Document={curDoc.layout} DataDoc={undefined} />
</div>
</>
);
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index b13753025..c0530ab81 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,5 +1,285 @@
@import '../global/globalCssVariables.scss';
-@import '../../../../node_modules/golden-layout/src/css/goldenlayout-base.css';
+
+.lm_root {
+ position: relative;
+}
+.lm_row > .lm_item {
+ float: left;
+}
+.lm_content {
+ overflow: hidden;
+ position: relative;
+}
+.lm_dragging,
+.lm_dragging * {
+ cursor: move !important;
+ user-select: none;
+}
+.lm_maximised {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 40;
+}
+.lm_maximise_placeholder {
+ display: none;
+}
+.lm_splitter {
+ position: relative;
+ z-index: 20;
+}
+.lm_splitter:hover,
+.lm_splitter.lm_dragging {
+ background: orange;
+}
+.lm_splitter.lm_vertical .lm_drag_handle {
+ width: 100%;
+ position: absolute;
+ cursor: ns-resize;
+}
+.lm_splitter.lm_horizontal {
+ float: left;
+ height: 100%;
+}
+.lm_splitter.lm_horizontal .lm_drag_handle {
+ height: 100%;
+ position: absolute;
+ cursor: ew-resize;
+}
+.lm_header {
+ overflow: visible;
+ position: relative;
+ z-index: 1;
+}
+// .lm_header [class^='lm_'] {
+// box-sizing: content-box !important;
+// }
+.lm_header .lm_controls {
+ position: absolute;
+ right: 3px;
+}
+.lm_header .lm_controls > li {
+ cursor: pointer;
+ float: left;
+ width: 18px;
+ height: 18px;
+ text-align: center;
+ top: 3px;
+}
+.lm_header ul {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+}
+.lm_header .lm_tabs {
+ position: absolute;
+}
+.lm_header .lm_tab {
+ cursor: pointer;
+ float: left;
+ height: 25px;
+ padding: 0 10px 5px;
+ padding-right: 25px;
+ position: relative;
+ box-shadow: unset !important;
+}
+.lm_header .lm_tab i {
+ width: 2px;
+ height: 19px;
+ position: absolute;
+}
+.lm_header .lm_tab i.lm_left {
+ top: 0;
+ left: -2px;
+}
+.lm_header .lm_tab i.lm_right {
+ top: 0;
+ right: -2px;
+}
+.lm_header .lm_tab .lm_title {
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.lm_header .lm_tab .lm_close_tab {
+ width: 14px;
+ height: 14px;
+ position: absolute;
+ top: 0;
+ right: 0;
+ text-align: center;
+}
+.lm_stack.lm_left .lm_header,
+.lm_stack.lm_right .lm_header {
+ height: 100%;
+}
+.lm_dragProxy.lm_left .lm_header,
+.lm_dragProxy.lm_right .lm_header,
+.lm_stack.lm_left .lm_header,
+.lm_stack.lm_right .lm_header {
+ width: 20px;
+ float: left;
+ vertical-align: top;
+}
+.lm_dragProxy.lm_left .lm_header .lm_tabs,
+.lm_dragProxy.lm_right .lm_header .lm_tabs,
+.lm_stack.lm_left .lm_header .lm_tabs,
+.lm_stack.lm_right .lm_header .lm_tabs {
+ transform-origin: left top;
+ top: 0;
+ width: 1000px;
+}
+.lm_dragProxy.lm_left .lm_header .lm_controls,
+.lm_dragProxy.lm_right .lm_header .lm_controls,
+.lm_stack.lm_left .lm_header .lm_controls,
+.lm_stack.lm_right .lm_header .lm_controls {
+ bottom: 0;
+}
+.lm_dragProxy.lm_left .lm_items,
+.lm_dragProxy.lm_right .lm_items,
+.lm_stack.lm_left .lm_items,
+.lm_stack.lm_right .lm_items {
+ float: left;
+}
+.lm_dragProxy.lm_left .lm_header .lm_tabs,
+.lm_stack.lm_left .lm_header .lm_tabs {
+ transform: rotate(-90deg) scaleX(-1);
+ left: 0;
+}
+.lm_dragProxy.lm_left .lm_header .lm_tabs .lm_tab,
+.lm_stack.lm_left .lm_header .lm_tabs .lm_tab {
+ transform: scaleX(-1);
+ margin-top: 1px;
+}
+.lm_dragProxy.lm_left .lm_header .lm_tabdropdown_list,
+.lm_stack.lm_left .lm_header .lm_tabdropdown_list {
+ top: initial;
+ right: initial;
+ left: 20px;
+}
+.lm_dragProxy.lm_right .lm_content {
+ float: left;
+}
+.lm_dragProxy.lm_right .lm_header .lm_tabs,
+.lm_stack.lm_right .lm_header .lm_tabs {
+ transform: rotate(90deg) scaleX(1);
+ left: 100%;
+ margin-left: 0;
+}
+.lm_dragProxy.lm_right .lm_header .lm_controls,
+.lm_stack.lm_right .lm_header .lm_controls {
+ left: 3px;
+}
+.lm_dragProxy.lm_right .lm_header .lm_tabdropdown_list,
+.lm_stack.lm_right .lm_header .lm_tabdropdown_list {
+ top: initial;
+ right: 20px;
+}
+.lm_dragProxy.lm_bottom .lm_header .lm_tab,
+.lm_stack.lm_bottom .lm_header .lm_tab {
+ margin-top: 0;
+ border-top: none;
+}
+.lm_dragProxy.lm_bottom .lm_header .lm_controls,
+.lm_stack.lm_bottom .lm_header .lm_controls {
+ top: 3px;
+}
+.lm_dragProxy.lm_bottom .lm_header .lm_tabdropdown_list,
+.lm_stack.lm_bottom .lm_header .lm_tabdropdown_list {
+ top: initial;
+ bottom: 20px;
+}
+.lm_drop_tab_placeholder {
+ float: left;
+ width: 100px;
+ height: 10px;
+ visibility: hidden;
+}
+.lm_header .lm_controls .lm_tabdropdown:before {
+ content: '';
+ width: 0;
+ height: 0;
+ vertical-align: middle;
+ display: inline-block;
+ border-top: 5px dashed;
+ border-right: 5px solid transparent;
+ border-left: 5px solid transparent;
+ color: white;
+}
+.lm_header .lm_tabdropdown_list {
+ position: absolute;
+ top: 20px;
+ right: 0;
+ z-index: 5;
+ overflow: hidden;
+}
+.lm_header .lm_tabdropdown_list .lm_tab {
+ clear: both;
+ padding-right: 10px;
+ margin: 0;
+}
+.lm_header .lm_tabdropdown_list .lm_tab .lm_title {
+ width: 100px;
+}
+.lm_header .lm_tabdropdown_list .lm_close_tab {
+ display: none !important;
+}
+.lm_dragProxy {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 30;
+}
+.lm_dragProxy .lm_header {
+ background: transparent;
+}
+.lm_dragProxy .lm_content {
+ border-top: none;
+ overflow: hidden;
+}
+.lm_dropTargetIndicator {
+ display: none;
+ position: absolute;
+ z-index: 20;
+}
+.lm_dropTargetIndicator .lm_inner {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ top: 0;
+ left: 0;
+}
+.lm_transition_indicator {
+ display: none;
+ width: 20px;
+ height: 20px;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 20;
+}
+.lm_popin {
+ width: 20px;
+ height: 20px;
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ z-index: 9999;
+}
+.lm_popin > * {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+.lm_popin > .lm_bg {
+ z-index: 10;
+}
+.lm_popin > .lm_icon {
+ z-index: 20;
+} /*# sourceMappingURL=goldenlayout-base.css.map */
+
@import '../../../../node_modules/golden-layout/src/css/goldenlayout-dark-theme.css';
.lm_title {
@@ -35,6 +315,8 @@
width: max-content;
height: 100%;
display: flex;
+ max-width: 100;
+ text-overflow: ellipsis;
}
.lm_active {
@@ -46,18 +328,33 @@
// font-weight: 700;
}
+.lm_header .lm_tabs {
+ overflow-y: hidden;
+ width: 100%;
+}
+ul.lm_tabs::before {
+ content: ' ';
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ z-index: 1;
+ pointer-events: none;
+ border: solid 1px black;
+}
.lm_header .lm_tab {
// padding: 0px; // moved to MainView.scss, othwerise they get overridden by default stylings
// opacity: 0.7;
// box-shadow: none;
// height: 25px;
// border-bottom: black solid;
+ border-bottom: unset !important;
+ border-top-right-radius: 5px;
+ border-top-left-radius: 5px;
.collectionDockingView-gear {
display: none;
}
}
-
.lm_header .lm_tab.lm_active {
padding: 0;
opacity: 1;
@@ -65,7 +362,11 @@
box-shadow: none;
height: 27px;
margin-right: 2px;
- // border-bottom: unset;
+ z-index: 2 !important;
+ border-right: solid 2px;
+ border-left: solid 2px;
+ border-top: solid 2px;
+ border-color: black;
.collectionDockingView-gear {
display: inline-block;
@@ -123,20 +424,55 @@
}
.lm_close_tab {
+ display: inline-flex !important;
padding: 0;
+ opacity: 1 !important;
align-self: center;
margin-right: 5px;
- background-color: black;
border-radius: 3px;
- opacity: 1 !important;
width: 15px !important;
height: 15px !important;
position: relative !important;
- display: inline-flex !important;
align-items: center;
top: 0 !important;
right: unset !important;
left: 0 !important;
+ background-image: unset !important;
+ &::before {
+ content: '\a0x\a0';
+ color: rgb(50, 50, 50);
+ margin: auto;
+ position: relative;
+ top: -2px;
+ }
+ &:hover {
+ &::before {
+ background: gray;
+ color: white;
+ }
+ }
+}
+.lm_close {
+ background-image: unset !important;
+ &:hover {
+ background: gray;
+ color: white !important;
+ }
+ &::before {
+ content: 'x';
+ margin: auto;
+ position: relative;
+ top: -2;
+ font-size: medium;
+ font-family: sans-serif;
+ }
+}
+
+.lm_iconWrap {
+ &:hover {
+ background: gray;
+ color: white !important;
+ }
}
.lm_tab,
@@ -154,14 +490,6 @@
top: 0;
left: 0;
- // overflow: hidden; // bcz: menus don't show up when this is on (e.g., the parentSelectorMenu)
- .collectionDockingView-gear {
- padding-left: 5px;
- height: 15px;
- width: 18px;
- margin: auto;
- }
-
.collectionDockingView-drag {
touch-action: none;
position: absolute;
@@ -180,7 +508,6 @@
display: flex;
align-content: center;
justify-content: center;
- background: $dark-gray;
}
.lm_controls > li {
@@ -190,14 +517,38 @@
}
.lm_controls .lm_popout {
- transform: rotate(45deg);
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAQUlEQVR4nHXOQQ4AMAgCQeT/f6aXpsGK3jSTuCVJAAr7iBdoAwCKd0nwfaAdHbYERw5b44+E8JoBjEYGMBq5gAYP3usUDu2IvoUAAAAASUVORK5CYII=);
+ background-image: unset;
+ left: -3;
+ &:hover {
+ background: gray;
+ color: white !important;
+ }
+ }
+ li.lm_popout::before {
+ content: '+';
+ margin: auto;
+ font-size: x-large;
+ top: -6;
+ position: relative;
+ }
+ .lm_maximise {
+ background-image: unset !important;
+ &::before {
+ content: '\25A3';
+ margin: auto;
+ font-size: medium;
+ position: relative;
+ }
+ &:hover {
+ background: gray;
+ color: white !important;
+ }
}
.lm_maximised .lm_controls .lm_maximise {
- opacity: 1;
- transform: scale(0.8);
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKElEQVR4nGP8////fwYCgImQAgYGBgYWKM2IR81/okwajIpgvsMbVgAwgQYRVakEKQAAAABJRU5ErkJggg==) !important;
+ &::before {
+ content: '\25A2';
+ }
}
.flexlayout__layout {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 519b7c905..4873a61ff 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -10,7 +10,7 @@ import { List } from '../../../fields/List';
import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { GetEffectiveAcl, inheritParentAcls } from '../../../fields/util';
-import { emptyFunction, incrementTitleCopy } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, incrementTitleCopy } from '../../../Utils';
import { DocServer } from '../../DocServer';
import { Docs } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
@@ -31,6 +31,7 @@ import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
import { TabDocView } from './TabDocView';
import React = require('react');
+import { SettingsManager } from '../../util/SettingsManager';
const _global = (window /* browser */ || global) /* node */ as any;
@observer
@@ -60,7 +61,7 @@ export class CollectionDockingView extends CollectionSubView() {
return this._goldenLayout._maximisedItem !== null;
}
private _goldenLayout: any = null;
-
+ static _highlightStyleSheet: any = addStyleSheet();
constructor(props: SubCollectionViewProps) {
super(props);
if (this.props.renderDepth < 0) runInAction(() => (CollectionDockingView.Instance = this));
@@ -118,6 +119,7 @@ export class CollectionDockingView extends CollectionSubView() {
const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
if (j !== -1) {
tab.header.parent.contentItems[j].remove();
+ CollectionDockingView.Instance.endUndoBatch();
return CollectionDockingView.Instance.layoutChanged();
}
}
@@ -329,6 +331,16 @@ export class CollectionDockingView extends CollectionSubView() {
width => !this._goldenLayout && width > 20 && setTimeout(() => this.setupGoldenLayout()), // need to wait for the collectiondockingview-container to have it's width/height since golden layout reads that to configure its windows
{ fireImmediately: true }
);
+ reaction(
+ () => [SettingsManager.userBackgroundColor, SettingsManager.userBackgroundColor],
+ () => {
+ clearStyleSheetRules(CollectionDockingView._highlightStyleSheet);
+ addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { background: `${SettingsManager.userBackgroundColor} !important` });
+ addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { color: `${SettingsManager.userColor} !important` });
+ addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${SettingsManager.userBackgroundColor} !important` });
+ },
+ { fireImmediately: true }
+ );
}
};
@@ -504,6 +516,23 @@ export class CollectionDockingView extends CollectionSubView() {
}
});
+ let addNewDoc = action(() => {
+ const dashboard = Doc.ActiveDashboard;
+ if (dashboard) {
+ dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
+ const docToAdd = Docs.Create.FreeformDocument([], {
+ _width: this.props.PanelWidth(),
+ _height: this.props.PanelHeight(),
+ _layout_fitWidth: true,
+ _freeform_backgroundGrid: true,
+ title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
+ });
+ Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd);
+ inheritParentAcls(this.dataDoc, docToAdd, false);
+ CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
+ }
+ });
+
stack.header?.controlsContainer
.find('.lm_close') //get the close icon
.off('click') //unbind the current click handler
@@ -523,31 +552,18 @@ export class CollectionDockingView extends CollectionSubView() {
})
);
+ stack.element.click((e: any) => {
+ if (stack.contentItems.length === 0 && Array.from(document.elementsFromPoint(e.originalEvent.x, e.originalEvent.y)).some(ele => ele?.className === 'empty-tabs-message')) {
+ addNewDoc();
+ }
+ });
stack.header?.controlsContainer
.find('.lm_maximise') //get the close icon
.click(() => setTimeout(this.stateChanged));
stack.header?.controlsContainer
.find('.lm_popout') //get the popout icon
.off('click') //unbind the current click handler
- .click(
- action(() => {
- // stack.config.fixed = !stack.config.fixed; // force the stack to have a fixed size
- const dashboard = Doc.ActiveDashboard;
- if (dashboard) {
- dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
- const docToAdd = Docs.Create.FreeformDocument([], {
- _width: this.props.PanelWidth(),
- _height: this.props.PanelHeight(),
- _layout_fitWidth: true,
- _freeform_backgroundGrid: true,
- title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
- });
- Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd);
- inheritParentAcls(this.dataDoc, docToAdd, false);
- CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
- }
- })
- );
+ .click(addNewDoc);
};
render() {
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 5c9dd2058..ec9d86c1a 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -41,6 +41,7 @@ import { COLLECTION_BORDER_WIDTH } from './CollectionView';
import { TabDocView } from './TabDocView';
import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionLinearView } from './collectionLinear';
+import { media_state } from '../nodes/AudioBox';
interface CollectionMenuProps {
panelHeight: () => number;
@@ -157,7 +158,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
<Toggle
toggleType={ToggleType.BUTTON}
type={Type.PRIM}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
onClick={this.toggleTopBar}
toggleStatus={SettingsManager.headerBarHeight > 0}
icon={<FontAwesomeIcon icon={headerIcon} size="lg" />}
@@ -166,7 +167,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
<Toggle
toggleType={ToggleType.BUTTON}
type={Type.PRIM}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
onClick={this.toggleProperties}
toggleStatus={SettingsManager.propertiesWidth > 0}
icon={<FontAwesomeIcon icon={propIcon} size="lg" />}
@@ -182,7 +183,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
className="collectionMenu-container"
style={{
background: SettingsManager.userBackgroundColor,
- // borderColor: StrCast(Doc.UserDoc().userColor)
+ // borderColor: SettingsManager.userColor
}}>
{this.contMenuButtons}
{hardCodedButtons}
@@ -579,7 +580,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
@undoBatch
@action
startRecording = () => {
- const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _layout_fitWidth: true, _width: 400, _height: 200, mediaState: 'pendingRecording' });
+ const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _layout_fitWidth: true, _width: 400, _height: 200, mediaState: media_state.PendingRecording });
CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
};
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 53a42d2a6..c7ad80f11 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,10 +1,10 @@
import React = require('react');
import { CursorProperty } from 'csstype';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, trace } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, Field, Opt } from '../../../fields/Doc';
import { DocData, Height, Width } from '../../../fields/DocSymbols';
-import { Id } from '../../../fields/FieldSymbols';
+import { Copy, Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
@@ -93,7 +93,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// we use availableWidth to convert from a percentage to a pixel count.
@computed get availableWidth() {
const numDividers = this.numGroupColumns - 1;
- return this.maxColWidth - numDividers * this.DividerWidth;
+ return this.maxColWidth - numDividers * this.DividerWidth - 2 * NumCast(this.layoutDoc.xMargin);
}
// children is passed as a prop to the NoteTakingField, which uses this function
@@ -216,7 +216,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
return this.props.styleProvider?.(doc, props, property);
};
- isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+ isContentActive = () => this.props.isContentActive();
blockPointerEventsWhenDragging = () => (this.docsDraggedRowCol.length ? 'none' : undefined);
// getDisplayDoc returns the rules for displaying a document in this view (ie. DocumentView)
@@ -269,7 +269,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
addDocTab={this.props.addDocTab}
bringToFront={returnFalse}
- scriptContext={this.props.scriptContext}
pinToPres={this.props.pinToPres}
/>
);
@@ -521,6 +520,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
this.observer.observe(ref);
}
}}
+ PanelWidth={this.props.PanelWidth}
select={this.props.select}
addDocument={this.addDocument}
chromeHidden={this.chromeHidden}
@@ -589,6 +589,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
const rightHeader = this.colHeaderData[colIndex + 1];
leftHeader.setWidth(leftHeader.width + movementX / this.availableWidth);
rightHeader.setWidth(rightHeader.width - movementX / this.availableWidth);
+ const headers = Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null);
+ headers.splice(headers.indexOf(leftHeader), 1, leftHeader[Copy]());
};
// renderedSections returns a list of all of the JSX elements used (columns and dividers). If the view
@@ -596,17 +598,15 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// allows the user to adjust the column widths.
@computed get renderedSections() {
TraceMobx();
- const entries = Array.from(this.Sections.entries());
- const sections = entries;
- const eles: JSX.Element[] = [];
- for (let i = 0; i < sections.length; i++) {
- const col = this.sectionNoteTaking(sections[i][0], sections[i][1]);
- eles.push(col);
- if (i < sections.length - 1) {
- eles.push(<CollectionNoteTakingViewDivider key={`divider${i}`} index={i} setColumnStartXCoords={this.setColumnStartXCoords} xMargin={this.xMargin} />);
- }
- }
- return eles;
+ const sections = Array.from(this.Sections.entries());
+ return sections.map((sec, i) => (
+ <>
+ {this.sectionNoteTaking(sec[0], sec[1])}
+ {i === sections.length - 1 ? null : ( //
+ <CollectionNoteTakingViewDivider key={`divider${i}`} isContentActive={this.isContentActive} index={i} setColumnStartXCoords={this.setColumnStartXCoords} xMargin={this.xMargin} />
+ )}
+ </>
+ ));
}
@computed get nativeWidth() {
@@ -621,7 +621,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
}
@computed get backgroundEvents() {
- return this.props.isContentActive() === false ? 'none' : undefined;
+ return this.isContentActive() === false ? 'none' : undefined;
}
observer: any;
@@ -636,7 +636,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
style={{
overflowY: this.props.isContentActive() ? 'auto' : 'hidden',
background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
- pointerEvents: this.backgroundEvents ? 'all' : undefined,
+ pointerEvents: this.backgroundEvents,
}}
onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
onPointerLeave={action(e => (this.docsDraggedRowCol.length = 0))}
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 3286d60bd..52cc21903 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -1,13 +1,13 @@
import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, trace } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { Copy, Id } from '../../../fields/FieldSymbols';
import { RichTextField } from '../../../fields/RichTextField';
import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
-import { Cast } from '../../../fields/Types';
+import { Cast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { returnEmptyString } from '../../../Utils';
@@ -50,6 +50,7 @@ interface CSVFieldColumnProps {
maxColWidth: number;
dividerWidth: number;
availableWidth: number;
+ PanelWidth: () => number;
}
/**
@@ -62,9 +63,9 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
// columnWidth returns the width of a column in absolute pixels
@computed get columnWidth() {
- if (!this.props.colHeaderData || !this.props.headingObject || this.props.colHeaderData.length === 1) return '100%';
+ if (!this.props.colHeaderData || !this.props.headingObject || this.props.colHeaderData.length === 1) return `${(this.props.availableWidth / this.props.PanelWidth()) * 100}%`;
const i = this.props.colHeaderData.indexOf(this.props.headingObject);
- return this.props.colHeaderData[i].width * 100 + '%';
+ return ((this.props.colHeaderData[i].width * this.props.availableWidth) / this.props.PanelWidth()) * 100 + '%';
}
private dropDisposer?: DragManager.DragDropDisposer;
@@ -297,6 +298,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
style={{
width: this.columnWidth,
background: this._background,
+ marginLeft: this.props.headings().findIndex((h: any) => h[0] === this.props.headingObject) === 0 ? NumCast(this.props.Document.xMargin) : 0,
}}
ref={this.createColumnDropRef}
onPointerEnter={this.pointerEntered}
diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index a1309b11f..af822d917 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -1,4 +1,5 @@
-import { action, observable } from 'mobx';
+import { action, observable, trace } from 'mobx';
+import { observer } from 'mobx-react';
import * as React from 'react';
import { emptyFunction, setupMoveUpEvents } from '../../../Utils';
import { UndoManager } from '../../util/UndoManager';
@@ -7,6 +8,7 @@ interface DividerProps {
index: number;
xMargin: number;
setColumnStartXCoords: (movementX: number, colIndex: number) => void;
+ isContentActive: () => boolean | undefined;
}
/**
@@ -14,24 +16,26 @@ interface DividerProps {
* which only appear when there is more than 1 column in CollectionNoteTakingView. Dividers
* are two simple vertical lines that allow the user to alter the widths of CollectionNoteTakingViewColumns.
*/
+@observer
export class CollectionNoteTakingViewDivider extends React.Component<DividerProps> {
@observable private isHoverActive = false;
@observable private isResizingActive = false;
@action
private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
- const batch = UndoManager.StartBatch('resizing');
+ let batch: UndoManager.Batch | undefined;
setupMoveUpEvents(
this,
e,
(e, down, delta) => {
+ if (!batch) batch = UndoManager.StartBatch('resizing');
this.props.setColumnStartXCoords(delta[0], this.props.index);
return false;
},
action(() => {
this.isResizingActive = false;
this.isHoverActive = false;
- batch.end();
+ batch?.end();
}),
emptyFunction
);
@@ -46,6 +50,7 @@ export class CollectionNoteTakingViewDivider extends React.Component<DividerProp
display: 'flex',
alignItems: 'center',
cursor: 'col-resize',
+ pointerEvents: this.props.isContentActive() ? 'all' : 'none',
}}
onPointerEnter={action(() => (this.isHoverActive = true))}
onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))}>
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index bbd528e13..91701b213 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,20 +1,19 @@
-import { action, computed, IReactionDisposer, reaction } from 'mobx';
+import { action, computed, IReactionDisposer } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, DocListCast } from '../../../fields/Doc';
import { Height, Width } from '../../../fields/DocSymbols';
+import { ScriptField } from '../../../fields/ScriptField';
import { NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
import { SelectionManager } from '../../util/SelectionManager';
-import { SnappingManager } from '../../util/SnappingManager';
import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { OpenWhere } from '../nodes/DocumentView';
+import { computePassLayout, computeStarburstLayout } from './collectionFreeForm';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import './CollectionPileView.scss';
import { CollectionSubView } from './CollectionSubView';
import React = require('react');
-import { ScriptField } from '../../../fields/ScriptField';
-import { OpenWhere } from '../nodes/DocumentView';
-import { computePassLayout, computeStarburstLayout } from './collectionFreeForm';
@observer
export class CollectionPileView extends CollectionSubView() {
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index d2be70577..c4650647c 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -32,7 +32,7 @@ import './CollectionStackedTimeline.scss';
export type CollectionStackedTimelineProps = {
Play: () => void;
Pause: () => void;
- playLink: (linkDoc: Doc) => void;
+ playLink: (linkDoc: Doc, options: DocFocusOptions) => void;
playFrom: (seekTimeInSeconds: number, endTime?: number) => void;
playing: () => boolean;
setTime: (time: number) => void;
@@ -58,9 +58,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
@observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
@observable public static CurrentlyPlaying: DocumentView[];
- static RangeScript: ScriptField;
static LabelScript: ScriptField;
- static RangePlayScript: ScriptField;
static LabelPlayScript: ScriptField;
private _timeline: HTMLDivElement | null = null; // ref to actual timeline div
@@ -114,25 +112,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
return this._zoomFactor;
}
- constructor(props: any) {
- super(props);
- // onClick play scripts
- CollectionStackedTimeline.RangeScript =
- CollectionStackedTimeline.RangeScript ||
- ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, {
- self: Doc.name,
- scriptContext: 'any',
- clientX: 'number',
- })!;
- CollectionStackedTimeline.RangePlayScript =
- CollectionStackedTimeline.RangePlayScript ||
- ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, {
- self: Doc.name,
- scriptContext: 'any',
- clientX: 'number',
- })!;
- }
-
componentDidMount() {
document.addEventListener('keydown', this.keyEvents, true);
}
@@ -161,6 +140,10 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this.layoutDoc.clipEnd = this.trimEnd;
this._trimming = TrimScope.None;
}
+ @action
+ public CancelTrimming() {
+ this._trimming = TrimScope.None;
+ }
@action
public setZoom(zoom: number) {
@@ -175,8 +158,19 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
return Math.max(this.clipStart, Math.min(this.clipEnd, (screen_delta / width) * this.clipDuration + this.clipStart));
};
- rangeClickScript = () => CollectionStackedTimeline.RangeScript;
- rangePlayScript = () => CollectionStackedTimeline.RangePlayScript;
+ @computed get rangeClick() {
+ // prettier-ignore
+ return ScriptField.MakeFunction('stackedTimeline.clickAnchor(this, clientX)',
+ { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: this as any }
+ )!;
+ }
+ @computed get rangePlay() {
+ // prettier-ignore
+ return ScriptField.MakeFunction('stackedTimeline.playOnClick(this, clientX)',
+ { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: this as any })!;
+ }
+ rangeClickScript = () => this.rangeClick;
+ rangePlayScript = () => this.rangePlay;
// handles key events for for creating key anchors, scrubbing, exiting trim
@action
@@ -677,7 +671,7 @@ interface StackedTimelineAnchorProps {
height: number;
toTimeline: (screen_delta: number, width: number) => number;
styleProvider?: (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any;
- playLink: (linkDoc: Doc) => void;
+ playLink: (linkDoc: Doc, options: DocFocusOptions) => void;
setTime: (time: number) => void;
startTag: string;
endTag: string;
@@ -793,7 +787,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) {
const anchor = observable({ view: undefined as any });
const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => {
- this.props.playLink(mark);
+ this.props.playLink(mark, options);
return undefined;
};
return {
@@ -831,7 +825,6 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
hideResizeHandles={true}
bringToFront={emptyFunction}
contextMenuItems={this.contextMenuItems}
- scriptContext={this.props.stackedTimeline}
/>
),
};
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 255bc3889..dddb3ec71 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -395,16 +395,23 @@
}
.collectionStackingView-addDocumentButton {
- font-size: 75%;
letter-spacing: 2px;
cursor: pointer;
+ .editableView-container-editing {
+ text-align: right;
+ }
.editableView-input {
outline-color: black;
letter-spacing: 2px;
color: grey;
border: 0px;
+ background: yellow;
+ text-align: right;
padding-top: 10px; // : 12px 10px 11px 10px;
+ input {
+ text-align: right;
+ }
}
}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index e4a0d6dad..c43a9d2b8 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -31,6 +31,7 @@ import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
import './CollectionStackingView.scss';
import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
import { CollectionSubView } from './CollectionSubView';
+import { SettingsManager } from '../../util/SettingsManager';
const _global = (window /* browser */ || global) /* node */ as any;
export type collectionStackingViewProps = {
@@ -67,7 +68,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
// it looks like this gets the column headers that Mehek was showing just now
@computed get colHeaderData() {
- return Cast(this.layoutDoc['_' + this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null);
+ return Cast(this.dataDoc['_' + this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null);
}
// Still not sure what a pivot is, but it appears that we can actually filter docs somehow?
@computed get pivotField() {
@@ -120,7 +121,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
if (this.colHeaderData === undefined) {
// TODO: what is a layout doc? Is it literally how this document is supposed to be layed out?
// here we're making an empty list of column headers (again, what Mehek showed us)
- this.layoutDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>();
+ this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>();
}
}
@@ -137,7 +138,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// assuming we need to get rowSpan because we might be dealing with many columns. Grid gap makes sense if multiple columns
const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
// just getting the style
- const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
+ const style = this.isStackingView ? { margin: this.rootDoc._stacking_alignCenter ? 'auto' : undefined, width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
// So we're choosing whether we're going to render a column or a masonry doc
return (
<div className={`collectionStackingView-${this.isStackingView ? 'columnDoc' : 'masonryDoc'}`} key={d[Id]} style={style}>
@@ -158,7 +159,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
if (!this.pivotField || this.colHeaderData instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
if (this.colHeaderData === undefined) {
- setTimeout(() => (this.layoutDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>()), 0);
+ setTimeout(() => (this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>()), 0);
return new Map<SchemaHeaderField, Doc[]>();
}
const colHeaderData = Array.from(this.colHeaderData);
@@ -207,7 +208,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// reset section headers when a new filter is inputted
this._pivotFieldDisposer = reaction(
() => this.pivotField,
- () => (this.layoutDoc['_' + this.fieldKey + '_columnHeaders'] = new List())
+ () => (this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List())
);
this._layout_autoHeightDisposer = reaction(
() => this.layoutDoc._layout_autoHeight,
@@ -360,7 +361,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
addDocTab={this.props.addDocTab}
bringToFront={returnFalse}
- scriptContext={this.props.scriptContext}
pinToPres={this.props.pinToPres}
/>
);
@@ -426,7 +426,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
className="collectionStackingView-columnDragger"
onPointerDown={this.columnDividerDown}
ref={this._draggerRef}
- style={{ cursor: this._cursor, color: StrCast(Doc.UserDoc().userColor), left: `${this.columnWidth + this.xMargin}px`, top: `${Math.max(0, this.yMargin - 9)}px` }}>
+ style={{ cursor: this._cursor, color: SettingsManager.userColor, left: `${this.columnWidth + this.xMargin}px`, top: `${Math.max(0, this.yMargin - 9)}px` }}>
<FontAwesomeIcon icon={'arrows-alt-h'} />
</div>
);
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 7a22d4871..3598d548a 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -212,15 +212,16 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
const layoutItems: ContextMenuProps[] = [];
const docItems: ContextMenuProps[] = [];
const dataDoc = this.props.DataDoc || this.props.Document;
-
+ const width = this._ele ? Number(getComputedStyle(this._ele).width.replace('px', '')) : 0;
+ const height = this._ele ? Number(getComputedStyle(this._ele).height.replace('px', '')) : 0;
DocUtils.addDocumentCreatorMenuItems(
doc => {
FormattedTextBox.SelectOnLoad = doc[Id];
return this.props.addDocument?.(doc);
},
this.props.addDocument,
- x,
- y,
+ 0,
+ 0,
true
);
@@ -272,8 +273,11 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
this.props.addDocument?.(created);
}
});
- const pt = this.props.screenToLocalTransform().inverse().transformPoint(x, y);
- ContextMenu.Instance.displayMenu(x, y, undefined, true);
+ const pt = this.props
+ .screenToLocalTransform()
+ .inverse()
+ .transformPoint(width - 30, height);
+ ContextMenu.Instance.displayMenu(pt[0], pt[1], undefined, true);
};
@computed get innards() {
@@ -357,7 +361,11 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
//TODO: would be great if there was additional space beyond the frame, so that you can actually see your
// bottom note
//TODO: ok, so we are using a single column, and this is it!
- <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton" style={{ width: 'calc(100% - 25px)', maxWidth: this.props.columnWidth / this.props.numGroupColumns - 25, marginBottom: 10 }}>
+ <div
+ key={`${heading}-add-document`}
+ onKeyDown={e => e.stopPropagation()}
+ className="collectionStackingView-addDocumentButton"
+ style={{ width: 'calc(100% - 25px)', maxWidth: this.props.columnWidth / this.props.numGroupColumns - 25, marginBottom: 10 }}>
<EditableView
GetValue={returnEmptyString}
SetValue={this.addNewTextDoc}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index eb4685834..158f9d8ee 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -31,7 +31,6 @@ export function CollectionSubView<X>(moreProps?: X) {
@observable _focusFilters: Opt<string[]>; // childFilters that are overridden when previewing a link to an anchor which has childFilters set on it
@observable _focusRangeFilters: Opt<string[]>; // childFiltersByRanges that are overridden when previewing a link to an anchor which has childFiltersByRanges set on it
protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
- //used for stacking and masonry view
this.dropDisposer?.();
this.gestureDisposer?.();
this._multiTouchDisposer?.();
@@ -210,11 +209,12 @@ export function CollectionSubView<X>(moreProps?: X) {
const targetDocments = DocListCast(this.dataDoc[this.props.fieldKey]);
const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag));
if (someMoved) docDragData.droppedDocuments = docDragData.droppedDocuments.map((drop, i) => (targetDocments.includes(docDragData.draggedDocuments[i]) ? docDragData.draggedDocuments[i] : drop));
- if ((!dropAction || dropAction === 'same' || dropAction === 'move' || someMoved) && docDragData.moveDocument) {
+ if ((!dropAction || dropAction === 'inSame' || dropAction === 'same' || dropAction === 'move' || someMoved) && docDragData.moveDocument) {
const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
if (movedDocs.length) {
- const canAdd = de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.rootDoc);
+ const canAdd =
+ (de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.rootDoc)) && (dropAction !== 'inSame' || docDragData.draggedDocuments.every(d => d.embedContainer === this.rootDoc));
const moved = docDragData.moveDocument(movedDocs, this.rootDoc, canAdd ? this.addDocument : returnFalse);
added = canAdd || moved ? moved : undefined;
} else {
@@ -298,7 +298,7 @@ export function CollectionSubView<X>(moreProps?: X) {
let source = split;
if (split.startsWith('data:image') && split.includes('base64')) {
const [{ accessPaths }] = await Networking.PostToServer('/uploadRemoteImage', { sources: [split] });
- if (accessPaths.agnostic.client.indexOf("dashblobstore") === -1) {
+ if (accessPaths.agnostic.client.indexOf('dashblobstore') === -1) {
source = Utils.prepend(accessPaths.agnostic.client);
} else {
source = accessPaths.agnostic.client;
@@ -347,10 +347,10 @@ export function CollectionSubView<X>(moreProps?: X) {
}
if (uriList || text) {
- if ((uriList || text).includes('www.youtube.com/watch') || text.includes('www.youtube.com/embed')) {
+ if ((uriList || text).includes('www.youtube.com/watch') || text.includes('www.youtube.com/embed') || text.includes('www.youtube.com/shorts')) {
const batch = UndoManager.StartBatch('youtube upload');
const generatedDocuments: Doc[] = [];
- this.slowLoadDocuments((uriList || text).split('v=')[1].split('&')[0], options, generatedDocuments, text, completed, addDocument).then(batch.end);
+ this.slowLoadDocuments((uriList || text).split('v=').lastElement().split('&')[0].split('shorts/').lastElement(), options, generatedDocuments, text, completed, addDocument).then(batch.end);
return;
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index eed04b3ee..9e5ac77d9 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -149,7 +149,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
if (isAlreadyInTree() !== sameTree) {
console.log('WHAAAT');
}
- dragData.dropAction = dropAction && !isAlreadyInTree() ? dropAction : sameTree ? 'same' : dragData.dropAction;
+ dragData.dropAction = dropAction && !isAlreadyInTree() ? dropAction : sameTree && dragData.dropAction !== 'inSame' ? 'same' : dragData.dropAction;
e.stopPropagation();
}
};
@@ -438,7 +438,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
render() {
TraceMobx();
- const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1) || 1;
+ const scale = this.props.NativeDimScaling?.() || 1;
return (
<div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
{!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeView_HasOverlay ? (
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 88f892efc..ce19b3f9b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,7 +1,7 @@
import { computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { ObjectField } from '../../../fields/ObjectField';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
@@ -40,7 +40,7 @@ interface CollectionViewProps_ extends FieldViewProps {
isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently)
layoutEngine?: () => string;
- setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void;
+ setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => void;
setBrushViewer?: (func?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void) => void;
ignoreUnrendered?: boolean;
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index f6acafa95..26aa5a121 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -17,6 +17,7 @@ import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
import { SelectionManager } from '../../util/SelectionManager';
+import { SettingsManager } from '../../util/SettingsManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoable, UndoManager } from '../../util/UndoManager';
@@ -131,6 +132,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
if (tab.element[0].children[1].children.length === 1) {
iconWrap.className = 'lm_iconWrap lm_moreInfo';
+ iconWrap.title = 'click for menu, drag to embed in document';
const dragBtnDown = (e: React.PointerEvent) => {
setupMoveUpEvents(
this,
@@ -215,15 +217,15 @@ export class TabDocView extends React.Component<TabDocViewProps> {
);
// highlight the tab when the tab document is brushed in any part of the UI
- tab._disposers.reactionDisposer = reaction(
- () => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }),
- ({ title, degree }) => {
- //titleEle.value = title;
- // titleEle.style.padding = degree ? 0 : 2;
- // titleEle.style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
- },
- { fireImmediately: true }
- );
+ // tab._disposers.reactionDisposer = reaction(
+ // () => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }),
+ // ({ title, degree }) => {
+ // titleEle.value = title;
+ // titleEle.style.padding = degree ? 0 : 2;
+ // titleEle.style.border = `${['gray', 'gray', 'gray'][degree]} ${['none', 'dashed', 'solid'][degree]} 2px`;
+ // },
+ // { fireImmediately: true }
+ // );
// clean up the tab when it is closed
tab.closeElement
@@ -278,10 +280,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
if (pinProps.pinViewport) PresBox.pinDocView(pinDoc, pinProps, anchorDoc ?? doc);
if (!pinProps?.audioRange && duration !== undefined) {
- pinDoc.mediaStart = 'manual';
- pinDoc.mediaStop = 'manual';
- pinDoc.config_clipStart = NumCast(doc.clipStart);
- pinDoc.config_clipEnd = NumCast(doc.clipEnd, duration);
+ pinDoc.presentation_mediaStart = 'manual';
+ pinDoc.presentation_mediaStop = 'manual';
}
if (pinProps?.activeFrame !== undefined) {
pinDoc.config_activeFrame = pinProps?.activeFrame;
@@ -350,7 +350,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
private onActiveContentItemChanged(contentItem: any) {
if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) {
this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab;
- if (!this._view) setTimeout(() => SelectionManager.SelectView(this._view, false));
+ if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== 'dontSelectOnActivate') setTimeout(() => SelectionManager.SelectView(this._view, false));
!this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
}
}
@@ -382,7 +382,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
return LightboxView.AddDocTab(doc, location);
case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods);
case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, undefined, keyValue);
- case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, undefined, keyValue);
+ case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, "dontSelectOnActivate", keyValue);
case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack, undefined, keyValue);
}
};
@@ -421,7 +421,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
PanelHeight = () => this._panelHeight;
miniMapColor = () => Colors.MEDIUM_GRAY;
tabView = () => this._view;
- disableMinimap = () => !this._document || this._document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._document)) || this._document?._type_collection !== CollectionViewType.Freeform;
+ disableMinimap = () => !this._document;
whenChildContentActiveChanges = (isActive: boolean) => (this._isAnyChildContentActive = isActive);
isContentActive = () => this._isContentActive;
waitForDoubleClick = () => (DocumentView.ExploreMode ? 'never' : undefined);
@@ -462,9 +462,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
bringToFront={emptyFunction}
pinToPres={TabDocView.PinDoc}
/>
- {this.disableMinimap() || this._document._type_collection !== CollectionViewType.Freeform ? null : (
- <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />
- )}
+ {this.disableMinimap() ? null : <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />}
</>
);
}
@@ -476,7 +474,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
style={{
fontFamily: Doc.UserDoc().renderStyle === 'comic' ? 'Comic Sans MS' : undefined,
}}
- onPointerEnter={action(() => (this._hovering = true))}
+ onPointerOver={action(() => (this._hovering = true))}
onPointerLeave={action(() => (this._hovering = false))}
onDragOver={action(() => (this._hovering = true))}
onDragLeave={action(() => (this._hovering = false))}
@@ -505,6 +503,18 @@ interface TabMinimapViewProps {
PanelHeight: () => number;
background: () => string;
}
+interface TabMiniThumbProps {
+ miniWidth: () => number;
+ miniHeight: () => number;
+ miniTop: () => number;
+ miniLeft: () => number;
+}
+@observer
+class TabMiniThumb extends React.Component<TabMiniThumbProps> {
+ render() {
+ return <div className="miniThumb" style={{ width: `${this.props.miniWidth()}% `, height: `${this.props.miniHeight()}% `, left: `${this.props.miniLeft()}% `, top: `${this.props.miniTop()}% ` }} />;
+ }
+}
@observer
export class TabMinimapView extends React.Component<TabMinimapViewProps> {
static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
@@ -516,25 +526,17 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
return 'none';
case StyleProp.DocContents:
const background = ((type: DocumentType) => {
+ // prettier-ignore
switch (type) {
- case DocumentType.PDF:
- return 'pink';
- case DocumentType.AUDIO:
- return 'lightgreen';
- case DocumentType.WEB:
- return 'brown';
- case DocumentType.IMG:
- return 'blue';
- case DocumentType.MAP:
- return 'orange';
- case DocumentType.VID:
- return 'purple';
- case DocumentType.RTF:
- return 'yellow';
- case DocumentType.COL:
- return undefined;
- default:
- return 'gray';
+ case DocumentType.PDF: return 'pink';
+ case DocumentType.AUDIO: return 'lightgreen';
+ case DocumentType.WEB: return 'brown';
+ case DocumentType.IMG: return 'blue';
+ case DocumentType.MAP: return 'orange';
+ case DocumentType.VID: return 'purple';
+ case DocumentType.RTF: return 'yellow';
+ case DocumentType.COL: return undefined;
+ default: return 'gray';
}
})(doc.type as DocumentType);
return !background ? undefined : <div style={{ width: doc[Width](), height: doc[Height](), position: 'absolute', display: 'block', background }} />;
@@ -554,13 +556,13 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
returnMiniSize = () => NumCast(this.props.document._miniMapSize, 150);
miniDown = (e: React.PointerEvent) => {
const doc = this.props.document;
- const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 };
const miniSize = this.returnMiniSize();
doc &&
setupMoveUpEvents(
this,
e,
action((e: PointerEvent, down: number[], delta: number[]) => {
+ const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 };
doc._freeform_panX = clamp(NumCast(doc._freeform_panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim);
doc._freeform_panY = clamp(NumCast(doc._freeform_panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim);
return false;
@@ -569,60 +571,57 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
emptyFunction
);
};
- render() {
- if (!this.renderBounds) return null;
- const miniWidth = (this.props.PanelWidth() / NumCast(this.props.document._freeform_scale, 1) / this.renderBounds.dim) * 100;
- const miniHeight = (this.props.PanelHeight() / NumCast(this.props.document._freeform_scale, 1) / this.renderBounds.dim) * 100;
- const miniLeft = 50 + ((NumCast(this.props.document._freeform_panX) - this.renderBounds.cx) / this.renderBounds.dim) * 100 - miniWidth / 2;
- const miniTop = 50 + ((NumCast(this.props.document._freeform_panY) - this.renderBounds.cy) / this.renderBounds.dim) * 100 - miniHeight / 2;
+ popup = () => {
+ if (!this.renderBounds) return <></>;
+ const renderBounds = this.renderBounds;
+ const miniWidth = () => (this.props.PanelWidth() / NumCast(this.props.document._freeform_scale, 1) / renderBounds.dim) * 100;
+ const miniHeight = () => (this.props.PanelHeight() / NumCast(this.props.document._freeform_scale, 1) / renderBounds.dim) * 100;
+ const miniLeft = () => 50 + ((NumCast(this.props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
+ const miniTop = () => 50 + ((NumCast(this.props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
const miniSize = this.returnMiniSize();
return (
- <div className="miniMap-hidden">
- <Popup
- icon={<FontAwesomeIcon icon="globe-asia" size="lg" />}
- color={StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE)}
- type={Type.TERT}
- onPointerDown={e => e.stopPropagation()}
- placement={'top-end'}
- popup={
- <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}>
- <CollectionFreeFormView
- Document={this.props.document}
- docViewPath={returnEmptyDoclist}
- childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
- noOverlay={true} // don't render overlay Docs since they won't scale
- setHeight={returnFalse}
- isContentActive={emptyFunction}
- isAnyChildContentActive={returnFalse}
- select={emptyFunction}
- isSelected={returnFalse}
- dontRegisterView={true}
- fieldKey={Doc.LayoutFieldKey(this.props.document)}
- bringToFront={emptyFunction}
- rootSelected={returnTrue}
- addDocument={returnFalse}
- moveDocument={returnFalse}
- removeDocument={returnFalse}
- PanelWidth={this.returnMiniSize}
- PanelHeight={this.returnMiniSize}
- ScreenToLocalTransform={Transform.Identity}
- renderDepth={0}
- whenChildContentsActiveChanged={emptyFunction}
- focus={emptyFunction}
- styleProvider={TabMinimapView.miniStyleProvider}
- addDocTab={this.props.addDocTab}
- pinToPres={TabDocView.PinDoc}
- childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
- childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
- searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
- fitContentsToBox={returnTrue}
- />
- <div className="miniOverlay" onPointerDown={this.miniDown}>
- <div className="miniThumb" style={{ width: `${miniWidth}% `, height: `${miniHeight}% `, left: `${miniLeft}% `, top: `${miniTop}% ` }} />
- </div>
- </div>
- }
+ <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}>
+ <CollectionFreeFormView
+ Document={this.props.document}
+ docViewPath={returnEmptyDoclist}
+ childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
+ noOverlay={true} // don't render overlay Docs since they won't scale
+ setHeight={returnFalse}
+ isContentActive={emptyFunction}
+ isAnyChildContentActive={returnFalse}
+ select={emptyFunction}
+ isSelected={returnFalse}
+ dontRegisterView={true}
+ fieldKey={Doc.LayoutFieldKey(this.props.document)}
+ bringToFront={emptyFunction}
+ rootSelected={returnTrue}
+ addDocument={returnFalse}
+ moveDocument={returnFalse}
+ removeDocument={returnFalse}
+ PanelWidth={this.returnMiniSize}
+ PanelHeight={this.returnMiniSize}
+ ScreenToLocalTransform={Transform.Identity}
+ renderDepth={0}
+ whenChildContentsActiveChanged={emptyFunction}
+ focus={emptyFunction}
+ styleProvider={TabMinimapView.miniStyleProvider}
+ addDocTab={this.props.addDocTab}
+ pinToPres={TabDocView.PinDoc}
+ childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+ childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
+ searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
+ fitContentsToBox={returnTrue}
/>
+ <div className="miniOverlay" onPointerDown={this.miniDown}>
+ <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} />
+ </div>
+ </div>
+ );
+ };
+ render() {
+ return this.props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this.props.document)) || this.props.document?._type_collection !== CollectionViewType.Freeform ? null : (
+ <div className="miniMap-hidden">
+ <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SettingsManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement={'top-end'} popup={this.popup} />
</div>
);
}
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index d22e85880..cbcc7c710 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -21,8 +21,28 @@
}
.treeView-bulletIcons {
- width: 100%;
+ margin: auto;
height: 100%;
+ // changes start here.
+
+ .treeView-expandIcon {
+ display: none;
+ left: -8px;
+ position: absolute;
+ }
+
+ .treeView-checkIcon {
+ left: 3.5px;
+ top: 2px;
+ position: absolute;
+ }
+
+ &:hover {
+ .treeView-expandIcon {
+ display: unset;
+ }
+ }
+ // end changes
position: relative;
display: flex;
flex-direction: row;
@@ -44,6 +64,8 @@
position: relative;
width: fit-content;
min-height: 20px;
+ min-width: 15px;
+ margin-right: 3px;
color: $medium-gray;
border: #80808030 1px solid;
border-radius: 5px;
@@ -121,7 +143,6 @@
filter: opacity(0.2) !important;
}
}
-
//align-items: center;
::-webkit-scrollbar {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 701150769..f89aa065b 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc';
import { DocData, Height, Width } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
@@ -36,6 +36,7 @@ import './TreeView.scss';
import React = require('react');
import { IconButton, Size } from 'browndash-components';
import { TreeSort } from './TreeSort';
+import { SettingsManager } from '../../util/SettingsManager';
export interface TreeViewProps {
treeView: CollectionTreeView;
@@ -222,6 +223,38 @@ export class TreeView extends React.Component<TreeViewProps> {
}
};
+ @undoBatch
+ @action
+ recurToggle = (childList: Doc[]) => {
+ if (childList.length > 0) {
+ childList.forEach(child => {
+ child.runProcess = !!!child.runProcess;
+ TreeView.ToggleChildrenRun.get(child)?.();
+ });
+ }
+ };
+
+ @undoBatch
+ @action
+ getRunningChildren = (childList: Doc[]) => {
+ if (childList.length === 0) {
+ return [];
+ }
+
+ const runningChildren: FieldResult[] = [];
+ childList.forEach(child => {
+ if (child.runProcess && TreeView.GetRunningChildren.get(child)) {
+ if (child.runProcess) {
+ runningChildren.push(child);
+ }
+ runningChildren.push(...(TreeView.GetRunningChildren.get(child)?.() ?? []));
+ }
+ });
+ return runningChildren;
+ };
+
+ static GetRunningChildren = new Map<Doc, any>();
+ static ToggleChildrenRun = new Map<Doc, () => void>();
constructor(props: any) {
super(props);
if (!TreeView._openLevelScript) {
@@ -230,6 +263,17 @@ export class TreeView extends React.Component<TreeViewProps> {
}
this._openScript = Doc.IsSystem(this.props.document) ? undefined : () => TreeView._openLevelScript!;
this._editTitleScript = Doc.IsSystem(this.props.document) ? () => TreeView._openLevelScript! : () => TreeView._openTitleScript!;
+
+ // set for child processing highligting
+ this.dataDoc.hasChildren = this.childDocs.length > 0;
+ // this.dataDoc.children = this.childDocs;
+ TreeView.ToggleChildrenRun.set(this.doc, () => {
+ this.recurToggle(this.childDocs);
+ });
+
+ TreeView.GetRunningChildren.set(this.doc, () => {
+ return this.getRunningChildren(this.childDocs);
+ });
}
_treeEle: any;
@@ -289,7 +333,7 @@ export class TreeView extends React.Component<TreeViewProps> {
const pt = [e.clientX, e.clientY];
const rect = this._header.current!.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length);
+ const inside = pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length);
this._header.current!.className = 'treeView-header';
if (!this.props.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.props.treeView.rootDoc) {
if (inside) this._header.current!.className += ' treeView-header-inside';
@@ -349,7 +393,7 @@ export class TreeView extends React.Component<TreeViewProps> {
if (!this._header.current) return false;
const rect = this._header.current.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
+ const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
if (de.complete.linkDragData) {
const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor();
const destDoc = this.doc;
@@ -389,9 +433,9 @@ export class TreeView extends React.Component<TreeViewProps> {
return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
};
const addDoc = inside ? localAdd : parentAddDoc;
- const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same') && moveDocument;
+ const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same' || dropAction === 'inSame') && moveDocument;
const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.treeView_FreezeChildren).includes('add')) || forceAdd;
- if (canAdd) {
+ if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this.props.parentTreeView?.doc))) {
this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true);
const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false);
this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = false);
@@ -554,7 +598,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
- !dataIsComputed && added && Doc.SetContainer(doc, DocCast(this.doc.embedContainer));
+ !dataIsComputed && added && Doc.SetContainer(doc, this.doc);
return added;
};
@@ -693,7 +737,7 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get renderBullet() {
TraceMobx();
const iconType = this.props.treeView.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : !this.childDocs.length ? ':empty' : '')) || 'question';
- const color = StrCast(Doc.UserDoc().userColor);
+ const color = SettingsManager.userColor;
const checked = this.onCheckedClick ? this.doc.treeView_Checked ?? 'unchecked' : undefined;
return (
<div
@@ -717,16 +761,15 @@ export class TreeView extends React.Component<TreeViewProps> {
<IconButton color={color} icon={<FontAwesomeIcon icon={[this.childDocs?.length && !this.treeViewOpen ? 'fas' : 'far', 'circle']} />} size={Size.XSMALL} />
)
) : (
- <div className="treeView-bulletIcons" style={{ color: Doc.IsSystem(DocCast(this.doc.proto)) ? 'red' : undefined }}>
- {this.onCheckedClick ? (
- <IconButton
- color={color}
- icon={<FontAwesomeIcon size="sm" icon={checked === 'check' ? 'check' : checked === 'x' ? 'times' : checked === 'unchecked' ? 'square' : !this.treeViewOpen ? 'caret-right' : 'caret-down'} />}
- size={Size.XSMALL}
+ <div className="treeView-bulletIcons">
+ <div className={`treeView-${this.onCheckedClick ? 'checkIcon' : 'expandIcon'}`}>
+ <FontAwesomeIcon
+ size="sm"
+ style={{ display: this.childDocs?.length >= 1 ? 'block' : 'none' }}
+ icon={checked === 'check' ? 'check' : checked === 'x' ? 'times' : checked === 'unchecked' ? 'square' : !this.treeViewOpen ? 'caret-right' : 'caret-down'}
/>
- ) : (
- <IconButton color={color} icon={<FontAwesomeIcon icon={iconType as IconProp} />} size={Size.XSMALL} />
- )}
+ </div>
+ {this.onCheckedClick ? null : typeof iconType === 'string' ? <FontAwesomeIcon icon={iconType as IconProp} /> : iconType}
</div>
)}
</div>
@@ -754,7 +797,7 @@ export class TreeView extends React.Component<TreeViewProps> {
@observable headerEleWidth = 0;
@computed get titleButtons() {
const customHeaderButtons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations);
- const color = StrCast(Doc.UserDoc().userColor);
+ const color = SettingsManager.userColor;
return this.props.treeViewHideHeaderFields() || this.doc.treeView_HideHeaderFields ? null : (
<>
{customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */}
@@ -924,8 +967,8 @@ export class TreeView extends React.Component<TreeViewProps> {
}
})}
Document={this.doc}
+ DataDoc={undefined} // or this.dataDoc?
layout_fitWidth={returnTrue}
- DataDoc={undefined}
scriptContext={this}
hideDecorationTitle={this.props.treeView.outlineMode}
hideResizeHandles={this.props.treeView.outlineMode}
@@ -972,8 +1015,8 @@ export class TreeView extends React.Component<TreeViewProps> {
ref={this._tref}
title="click to edit title. Double Click or Drag to Open"
style={{
- backgroundColor: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? StrCast(Doc.UserDoc().userVariantColor) : undefined,
- color: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? lightOrDark(StrCast(Doc.UserDoc().userVariantColor)) : undefined,
+ backgroundColor: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? SettingsManager.userVariantColor : undefined,
+ color: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? lightOrDark(SettingsManager.userVariantColor) : undefined,
fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? 'bold' : undefined,
textDecoration: Doc.GetT(this.doc, 'title', 'string', true) ? 'underline' : undefined,
outline: this.doc === Doc.ActiveDashboard ? 'dashed 1px #06123232' : undefined,
@@ -1002,7 +1045,7 @@ export class TreeView extends React.Component<TreeViewProps> {
<div
className="treeView-background"
style={{
- background: StrCast(Doc.UserDoc().userColor),
+ background: SettingsManager.userColor,
}}
/>
{contents}
@@ -1098,7 +1141,7 @@ export class TreeView extends React.Component<TreeViewProps> {
const pt = [de.clientX, de.clientY];
const rect = this._header.current!.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
+ const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false));
};
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 1e76d373c..15b6e1d37 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -350,7 +350,7 @@ export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc:
groupNames.push({ type: 'text', text: toLabel(Math.ceil(maxTime)), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
}
- const divider = { type: 'div', color: Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'dimgray' : 'black', x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
+ const divider = { type: 'div', color: 'black', x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]);
function layoutDocsAtTime(keyDocs: Doc[], key: number) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 89deb733a..24a758d8c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -228,6 +228,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetX);
const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetY);
+ const link = this.props.LinkDocs[0];
return {
a,
b,
@@ -238,11 +239,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
textX,
textY,
// fully connected
- pt1,
- pt2,
+ // pt1,
+ // pt2,
// this code adds space between links
- // pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13],
- // pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13],
+ pt1: link.link_displayArrow ? [pt1[0] + pt1normalized[0] * 3*NumCast(link.link_displayArrow_scale, 4), pt1[1] + pt1normalized[1] * 3*NumCast(link.link_displayArrow_scale, 3)] : pt1,
+ pt2: link.link_displayArrow ? [pt2[0] + pt2normalized[0] * 3*NumCast(link.link_displayArrow_scale, 4), pt2[1] + pt2normalized[1] * 3*NumCast(link.link_displayArrow_scale, 3)] : pt2,
};
}
@@ -256,9 +257,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const linkColorList = Doc.UserDoc().link_ColorList as List<string>;
const linkRelationshipSizes = Doc.UserDoc().link_relationshipSizes as List<number>;
const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship);
- const linkDescription = Field.toString(link.link_description as any as Field);
+ const linkDescription = Field.toString(link.link_description as any as Field).split('\n')[0];
- const linkSize = currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex];
+ const linkSize = Doc.noviceMode || currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex];
//access stroke color using index of the relationship in the color list (default black)
const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? StrCast(link._backgroundColor, 'black') : linkColorList[currRelationshipIndex];
@@ -268,15 +269,12 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
//thickness varies linearly from 3px to 12px for increasing link count
const strokeWidth = linkSize === -1 ? '3px' : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + 'px';
- if (link.link_displayArrow === undefined) {
- link.link_displayArrow = false;
- }
-
+ const arrowScale = NumCast(link.link_displayArrow_scale, 3)
return link.opacity === 0 || !a.width || !b.width || (!(Doc.UserDoc().showLinkLines || link.link_displayLine) && !aActive && !bActive) ? null : (
<>
<defs>
- <marker id={`${link[Id] + 'arrowhead'}`} markerWidth="4" markerHeight="3" refX="0" refY="1.5" orient="auto">
- <polygon points="0 0, 3 1.5, 0 3" fill={stroke} />
+ <marker id={`${link[Id] + 'arrowhead'}`} markerWidth={`${4*arrowScale}`} markerHeight={`${3*arrowScale}`} refX="0" refY={`${1.5*arrowScale}`} orient="auto">
+ <polygon points={`0 0, ${3*arrowScale} ${1.5*arrowScale}, 0 ${3*arrowScale}`} fill={stroke} />
</marker>
<filter id="outline">
<feMorphology in="SourceAlpha" result="expanded" operator="dilate" radius="1" />
@@ -306,7 +304,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
{textX === undefined || !linkDescription ? null : (
<text filter={`url(#${link[Id] + 'background'})`} className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown}>
<tspan>&nbsp;</tspan>
- <tspan dy="2">{linkDescription}</tspan>
+ <tspan dy="2">{linkDescription.substring(0, 50) + (linkDescription.length > 50 ? '...' : '')}</tspan>
<tspan dy="2">&nbsp;</tspan>
</text>
)}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index e4ae251c8..c90fdf013 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -11,6 +11,15 @@
touch-action: none;
border-radius: inherit;
}
+.collectionFreeForm-groupDropper {
+ width: 10000;
+ height: 10000;
+ left: -5000;
+ top: -5000;
+ position: absolute;
+ background: transparent;
+ pointer-events: all;
+}
.collectionfreeformview-grid {
transform-origin: top left;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 16d6f1270..676e96714 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -16,7 +16,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro
import { ImageField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, emptyFunction, intersectRect, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, emptyFunction, intersectRect, lightOrDark, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -33,7 +33,6 @@ import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariables.scss';
import { Timeline } from '../../animationtimeline/Timeline';
import { ContextMenu } from '../../ContextMenu';
-import { DocumentDecorations } from '../../DocumentDecorations';
import { GestureOverlay } from '../../GestureOverlay';
import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
import { LightboxView } from '../../LightboxView';
@@ -52,6 +51,7 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso
import './CollectionFreeFormView.scss';
import { MarqueeView } from './MarqueeView';
import React = require('react');
+import { FollowLinkScript } from '../../../util/LinkFollower';
export type collectionFreeformViewProps = {
NativeWidth?: () => number;
@@ -97,7 +97,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private _brushtimer: any;
private _brushtimer1: any;
- private get isAnnotationOverlay() {
+ public get isAnnotationOverlay() {
return this.props.isAnnotationOverlay;
}
public get scaleFieldKey() {
@@ -124,7 +124,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _timelineRef = React.createRef<Timeline>();
@observable _marqueeRef: HTMLDivElement | null = null;
@observable _marqueeViewRef = React.createRef<MarqueeView>();
- @observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged.
+ @observable GroupChildDrag: boolean = false; // child document view being dragged. needed to update drop areas of groups when a group item is dragged.
@observable _brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 }; // highlighted region of freeform canvas used by presentations to indicate a region
constructor(props: any) {
@@ -155,7 +155,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
: this.props.contentBounds?.() ??
aggregateBounds(
- this._layoutElements.filter(e => e.bounds && e.bounds.width && !e.bounds.z).map(e => e.bounds!),
+ this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!),
NumCast(this.layoutDoc._xPadding, 10),
NumCast(this.layoutDoc._yPadding, 10)
);
@@ -186,7 +186,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
}
@computed get cachedGetTransform(): Transform {
- return this.getContainerTransform().translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
+ return this.getContainerTransform()
+ .scale(this.props.isAnnotationOverlay ? 1 : 1 / this.nativeDim())
+ .translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY)
+ .transform(this.cachedGetLocalTransform);
}
public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) {
@@ -311,6 +314,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
focus = (anchor: Doc, options: DocFocusOptions) => {
if (this._lightboxDoc) return;
+ if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor)) return;
const xfToCollection = options?.docTransform ?? Transform.Identity();
const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
@@ -338,7 +342,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
- if (!de.embedKey && !this.ChildDrag && this.rootDoc._isGroup) return false;
if (!super.onInternalDrop(e, de)) return false;
const refDoc = docDragData.droppedDocuments[0];
const [xpo, ypo] = this.getContainerTransform().transformPoint(de.x, de.y);
@@ -420,11 +423,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (linkDragData.linkDragView.props.docViewPath().includes(this.props.docViewPath().lastElement())) {
let added = false;
// do nothing if link is dropped into any freeform view parent of dragged document
- if (!linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.rootDoc) {
- const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' });
- added = this.props.addDocument?.(source) ? true : false;
- de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
- }
+ const source =
+ !linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.rootDoc
+ ? Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' })
+ : Docs.Create.FontIconDocument({
+ title: 'anchor',
+ icon_label: '',
+ followLinkToggle: true,
+ icon: 'map-pin',
+ x: xp,
+ y: yp,
+ backgroundColor: '#ACCEF7',
+ layout_hideAllLinks: true,
+ layout_hideLinkButton: true,
+ _width: 15,
+ _height: 15,
+ _xPadding: 0,
+ onClick: FollowLinkScript(),
+ });
+ added = this.props.addDocument?.(source) ? true : false;
+ de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
+
e.stopPropagation();
!added && e.preventDefault();
return added;
@@ -844,7 +863,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// create a new curve by appending all curves of the current segment together in order to render a single new stroke.
if (!e.shiftKey) {
this._eraserLock++;
- this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment =>
+ const segments = this.segmentInkStroke(intersect.inkView, intersect.t);
+ segments.forEach(segment =>
this.forceStrokeGesture(
e,
GestureUtils.Gestures.Stroke,
@@ -1023,7 +1043,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
zoom = (pointX: number, pointY: number, deltaY: number): void => {
- if (this.Document._isGroup || this.Document._freeform_noZoom) return;
+ if (this.Document._isGroup || this.Document[(this.props.viewField ?? '_') + 'freeform_noZoom']) return;
let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05;
if (deltaScale < 0) deltaScale = -deltaScale;
const [x, y] = this.getTransform().transformPoint(pointX, pointY);
@@ -1031,12 +1051,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (deltaScale * invTransform.Scale > 20) {
deltaScale = 20 / invTransform.Scale;
}
- if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc._freeform_scale_min, 1) && this.isAnnotationOverlay) {
+ if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc[this.scaleFieldKey + '_min'])) {
this.setPan(0, 0);
return;
}
- if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._freeform_scale_min, 1) && this.isAnnotationOverlay) {
- deltaScale = NumCast(this.rootDoc._freeform_scale_min, 1) / invTransform.Scale;
+ if (deltaScale * invTransform.Scale > NumCast(this.rootDoc[this.scaleFieldKey + '_max'], Number.MAX_VALUE)) {
+ deltaScale = NumCast(this.rootDoc[this.scaleFieldKey + '_max'], 1) / invTransform.Scale;
+ }
+ if (deltaScale * invTransform.Scale < NumCast(this.rootDoc[this.scaleFieldKey + '_min'], this.isAnnotationOverlay ? 1 : 0)) {
+ deltaScale = NumCast(this.rootDoc[this.scaleFieldKey + '_min'], 1) / invTransform.Scale;
}
const localTransform = invTransform.scaleAbout(deltaScale, x, y);
@@ -1526,12 +1549,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
});
});
- if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) {
- // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar
- if (this.props.viewField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling());
- else this.props.Document[this.scaleFieldKey] = Math.max(1, this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey]));
- }
-
this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
return elements;
}
@@ -1745,19 +1762,24 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@undoBatch
toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight);
+ ///
+ /// resetView restores a freeform collection to unit scale and centered at (0,0) UNLESS
+ /// the view is a group, in which case this does nothing (since Groups calculate their own scale and center)
+ ///
+ @undoBatch
+ resetView = () => {
+ if (!this.props.Document._isGroup) {
+ this.props.Document[this.panXFieldKey] = this.props.Document[this.panYFieldKey] = 0;
+ this.props.Document[this.scaleFieldKey] = 1;
+ }
+ };
+
onContextMenu = (e: React.MouseEvent) => {
if (this.props.isAnnotationOverlay || !ContextMenu.Instance) return;
const appearance = ContextMenu.Instance.findByDescription('Appearance...');
const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
- appearanceItems.push({
- description: 'Reset View',
- event: () => {
- this.props.Document[this.panXFieldKey] = this.props.Document[this.panYFieldKey] = 0;
- this.props.Document[this.scaleFieldKey] = 1;
- },
- icon: 'compress-arrows-alt',
- });
+ !this.props.Document._isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
!Doc.noviceMode &&
appearanceItems.push({
description: 'Toggle auto arrange',
@@ -1885,6 +1907,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
showPresPaths = () => (CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.getPaths(this.rootDoc) : null);
brushedView = () => this._brushedView;
+ gridColor = () => {
+ const backColor = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
+ return lightOrDark(backColor);
+ };
@computed get marqueeView() {
TraceMobx();
return (
@@ -1917,6 +1943,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
PanelHeight={this.props.PanelHeight}
panX={this.panX}
panY={this.panY}
+ color={this.gridColor}
nativeDimScaling={this.nativeDim}
zoomScaling={this.zoomScaling}
layoutDoc={this.layoutDoc}
@@ -1954,15 +1981,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
nativeDim = () => this.nativeDimScaling;
- private groupDropDisposer?: DragManager.DragDropDisposer;
- protected createGroupEventsTarget = (ele: HTMLDivElement) => {
- //used for stacking and masonry view
- this.groupDropDisposer?.();
- if (ele) {
- this.groupDropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc, this.onInternalPreDrop.bind(this));
- }
- };
-
@action
brushView = (viewport: { width: number; height: number; panX: number; panY: number }, transTime: number) => {
this._brushtimer1 && clearTimeout(this._brushtimer1);
@@ -2047,30 +2065,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
{/* // uncomment to show snap lines */}
<div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
<svg style={{ width: '100%', height: '100%' }}>
- {this._hLines?.map(l => (
- <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />
- ))}
- {this._vLines?.map(l => (
- <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
- ))}
+ {(this._hLines ?? [])
+ .map(l => <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />) //
+ .concat((this._vLines ?? []).map(l => <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />)) ?? []}
</svg>
</div>
- {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? (
- <div
- className="collectionFreeForm-groupDropper"
- ref={this.createGroupEventsTarget}
- style={{
- width: this.ChildDrag ? '10000' : '100%',
- height: this.ChildDrag ? '10000' : '100%',
- left: this.ChildDrag ? '-5000' : 0,
- top: this.ChildDrag ? '-5000' : 0,
- position: 'absolute',
- background: '#0009930',
- pointerEvents: 'all',
- }}
- />
- ) : null}
+ {this.GroupChildDrag ? <div className="collectionFreeForm-groupDropper" /> : null}
</>
)}
</div>
@@ -2216,6 +2217,7 @@ interface CollectionFreeFormViewBackgroundGridProps {
panY: () => number;
PanelWidth: () => number;
PanelHeight: () => number;
+ color: () => string;
isAnnotationOverlay?: boolean;
nativeDimScaling: () => number;
zoomScaling: () => number;
@@ -2237,7 +2239,7 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
const renderGridSpace = gridSpace * this.props.zoomScaling();
const w = this.props.PanelWidth() / this.props.nativeDimScaling() + 2 * renderGridSpace;
const h = this.props.PanelHeight() / this.props.nativeDimScaling() + 2 * renderGridSpace;
- const strokeStyle = Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'rgba(255,255,255,0.5)' : 'rgba(0, 0,0,0.5)';
+ const strokeStyle = this.props.color();
return !this.props.nativeDimScaling() ? null : (
<canvas
className="collectionFreeFormView-grid"
@@ -2285,7 +2287,7 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
}
while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview
- ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5, browseTransitionTime);
+ ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
Doc.linkFollowHighlight(dv?.props.Document, false);
}
});
@@ -2320,9 +2322,3 @@ ScriptingGlobals.add(function bringToFront() {
ScriptingGlobals.add(function sendToBack(doc: Doc) {
SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc, true));
});
-ScriptingGlobals.add(function resetView() {
- SelectionManager.Docs().forEach(doc => {
- doc._freeform_panX = doc._freeform_panY = 0;
- doc._freeform_scale = 1;
- });
-});
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 71900c63f..607f9fb95 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -8,6 +8,7 @@ import { IconButton } from 'browndash-components';
import { StrCast } from '../../../../fields/Types';
import { Doc } from '../../../../fields/Doc';
import { computed } from 'mobx';
+import { SettingsManager } from '../../../util/SettingsManager';
@observer
export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -27,43 +28,18 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
}
@computed get userColor() {
- return StrCast(Doc.UserDoc().userColor)
+ return SettingsManager.userColor;
}
render() {
const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ width: 19 }} />;
const buttons = (
<>
- <IconButton
- tooltip={"Create a Collection"}
- onPointerDown={this.createCollection}
- icon={<FontAwesomeIcon icon="object-group"/>}
- color={this.userColor}
- />
- <IconButton
- tooltip={"Create a Grouping"}
- onPointerDown={e => this.createCollection(e, true)}
- icon={<FontAwesomeIcon icon="layer-group"/>}
- color={this.userColor}
- />
- <IconButton
- tooltip={"Summarize Documents"}
- onPointerDown={this.summarize}
- icon={<FontAwesomeIcon icon="compress-arrows-alt"/>}
- color={this.userColor}
- />
- <IconButton
- tooltip={"Delete Documents"}
- onPointerDown={this.delete}
- icon={<FontAwesomeIcon icon="trash-alt"/>}
- color={this.userColor}
- />
- <IconButton
- tooltip={"Pin selected region"}
- onPointerDown={this.pinWithView}
- icon={<FontAwesomeIcon icon="map-pin"/>}
- color={this.userColor}
- />
+ <IconButton tooltip={'Create a Collection'} onPointerDown={this.createCollection} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} />
+ <IconButton tooltip={'Create a Grouping'} onPointerDown={e => this.createCollection(e, true)} icon={<FontAwesomeIcon icon="layer-group" />} color={this.userColor} />
+ <IconButton tooltip={'Summarize Documents'} onPointerDown={this.summarize} icon={<FontAwesomeIcon icon="compress-arrows-alt" />} color={this.userColor} />
+ <IconButton tooltip={'Delete Documents'} onPointerDown={this.delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={this.userColor} />
+ <IconButton tooltip={'Pin selected region'} onPointerDown={this.pinWithView} icon={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} />
</>
);
return this.getElement(buttons);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index cd7bd28e9..4c502021d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -18,7 +18,7 @@ import { SelectionManager } from '../../../util/SelectionManager';
import { Transform } from '../../../util/Transform';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
-import { OpenWhere } from '../../nodes/DocumentView';
+import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
import { PreviewCursor } from '../../PreviewCursor';
@@ -335,7 +335,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
if (!(e.nativeEvent as any).marqueeHit) {
(e.nativeEvent as any).marqueeHit = true;
if (!this.props.trySelectCluster(e.shiftKey)) {
- this.setPreviewCursor(e.clientX, e.clientY, false, false, this.props.Document);
+ !DocumentView.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this.props.Document);
} else e.stopPropagation();
}
}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 4bb5c5adf..faf7501c4 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,5 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
+import { Toggle, ToggleType, Type } from 'browndash-components';
import { action, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -7,22 +8,20 @@ import { Doc, Opt } from '../../../../fields/Doc';
import { Height, Width } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnTrue, StopEvent, Utils } from '../../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils';
import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager, dropActionType } from '../../../util/DragManager';
+import { SettingsManager } from '../../../util/SettingsManager';
import { Transform } from '../../../util/Transform';
import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
import { DocumentView } from '../../nodes/DocumentView';
import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
-import { StyleProp } from '../../StyleProvider';
import { UndoStack } from '../../UndoStack';
import { CollectionStackedTimeline } from '../CollectionStackedTimeline';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionLinearView.scss';
-import { Button, Toggle, ToggleType, Type } from 'browndash-components';
-import { Colors } from '../../global/globalEnums';
-import { SettingsManager } from '../../../util/SettingsManager';
/**
* CollectionLinearView is the class for rendering the horizontal collection
@@ -146,6 +145,7 @@ export class CollectionLinearView extends CollectionSubView() {
case '<LinkingUI>': return this.getLinkUI();
case '<CurrentlyPlayingUI>': return this.getCurrentlyPlayingUI();
case '<UndoStack>': return <UndoStack key={doc[Id]} width={200} height={40} inline={true} />;
+ case '<Branching>': return Doc.UserDoc().isBranchingMode ? <BranchingTrailManager /> : null;
}
const nested = doc._type_collection === CollectionViewType.Linear;
@@ -227,7 +227,7 @@ export class CollectionLinearView extends CollectionSubView() {
<div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension(), pointerEvents: 'all' }}>
{
<>
- {menuOpener}
+ {!this.layoutDoc.linearView_Expandable ? null : menuOpener}
{!this.layoutDoc.linearView_IsOpen ? null : (
<div
className="collectionLinearView-content"
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 80da4e1a2..81453c0b8 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,4 +1,5 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@mui/material';
import { Button } from 'browndash-components';
import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
@@ -7,6 +8,7 @@ import { Doc, DocListCast } from '../../../../fields/Doc';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { returnFalse } from '../../../../Utils';
import { DragManager, dropActionType } from '../../../util/DragManager';
+import { SettingsManager } from '../../../util/SettingsManager';
import { Transform } from '../../../util/Transform';
import { undoable, undoBatch } from '../../../util/UndoManager';
import { DocumentView } from '../../nodes/DocumentView';
@@ -301,13 +303,13 @@ export class CollectionMulticolumnView extends CollectionSubView() {
.scale(this.props.NativeDimScaling?.() || 1);
const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
collector.push(
- <div className="document-wrapper" key={'wrapper' + i} style={{ width: width() }}>
- {this.getDisplayDoc(layout, dxf, docwidth, docheight, shouldNotScale)}
- <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={undoable(e => {
- this.props.removeDocument?.(layout);
- }, "close doc")} color={StrCast(Doc.UserDoc().userColor)} />
- <WidthLabel layout={layout} collectionDoc={Document} />
- </div>,
+ <Tooltip title={'Tab: ' + StrCast(layout.title)}>
+ <div className="document-wrapper" key={'wrapper' + i} style={{ width: width() }}>
+ {this.getDisplayDoc(layout, dxf, docwidth, docheight, shouldNotScale)}
+ <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this.props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
+ <WidthLabel layout={layout} collectionDoc={Document} />
+ </div>
+ </Tooltip>,
<ResizeBar
width={resizerWidth}
key={'resizer' + i}
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index 273e609ca..868b1140d 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -1,12 +1,12 @@
-import * as React from "react";
-import { observer } from "mobx-react";
-import { observable, action } from "mobx";
-import { Doc } from "../../../../fields/Doc";
-import { NumCast, StrCast } from "../../../../fields/Types";
-import { DimUnit } from "./CollectionMulticolumnView";
-import { UndoManager } from "../../../util/UndoManager";
-import { StyleProviderFunc } from "../../nodes/DocumentView";
-import { StyleProp } from "../../StyleProvider";
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { observable, action } from 'mobx';
+import { Doc } from '../../../../fields/Doc';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { DimUnit } from './CollectionMulticolumnView';
+import { UndoManager } from '../../../util/UndoManager';
+import { StyleProviderFunc } from '../../nodes/DocumentView';
+import { StyleProp } from '../../StyleProvider';
interface ResizerProps {
width: number;
@@ -31,13 +31,13 @@ export default class ResizeBar extends React.Component<ResizerProps> {
this.props.select(false);
e.stopPropagation();
e.preventDefault();
- window.removeEventListener("pointermove", this.onPointerMove);
- window.removeEventListener("pointerup", this.onPointerUp);
- window.addEventListener("pointermove", this.onPointerMove);
- window.addEventListener("pointerup", this.onPointerUp);
+ window.removeEventListener('pointermove', this.onPointerMove);
+ window.removeEventListener('pointerup', this.onPointerUp);
+ window.addEventListener('pointermove', this.onPointerMove);
+ window.addEventListener('pointerup', this.onPointerUp);
this.isResizingActive = true;
- this._resizeUndo = UndoManager.StartBatch("multcol resizing");
- }
+ this._resizeUndo = UndoManager.StartBatch('multcol resizing');
+ };
private onPointerMove = ({ movementX }: PointerEvent) => {
const { toLeft, toRight, columnUnitLength } = this.props;
@@ -47,30 +47,30 @@ export default class ResizeBar extends React.Component<ResizerProps> {
const unitLength = columnUnitLength();
if (unitLength) {
if (toNarrow) {
- const scale = StrCast(toNarrow._dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
+ const scale = StrCast(toNarrow._dimUnit, '*') === DimUnit.Ratio ? unitLength : 1;
toNarrow._dimMagnitude = Math.max(0.05, NumCast(toNarrow._dimMagnitude, 1) - Math.abs(movementX) / scale);
}
if (toWiden) {
- const scale = StrCast(toWiden._dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
+ const scale = StrCast(toWiden._dimUnit, '*') === DimUnit.Ratio ? unitLength : 1;
toWiden._dimMagnitude = Math.max(0.05, NumCast(toWiden._dimMagnitude, 1) + Math.abs(movementX) / scale);
}
}
- }
+ };
private get isActivated() {
const { toLeft, toRight } = this.props;
if (toLeft && toRight) {
- if (StrCast(toLeft._dimUnit, "*") === DimUnit.Pixel && StrCast(toRight._dimUnit, "*") === DimUnit.Pixel) {
+ if (StrCast(toLeft._dimUnit, '*') === DimUnit.Pixel && StrCast(toRight._dimUnit, '*') === DimUnit.Pixel) {
return false;
}
return true;
} else if (toLeft) {
- if (StrCast(toLeft._dimUnit, "*") === DimUnit.Pixel) {
+ if (StrCast(toLeft._dimUnit, '*') === DimUnit.Pixel) {
return false;
}
return true;
} else if (toRight) {
- if (StrCast(toRight._dimUnit, "*") === DimUnit.Pixel) {
+ if (StrCast(toRight._dimUnit, '*') === DimUnit.Pixel) {
return false;
}
return true;
@@ -82,23 +82,25 @@ export default class ResizeBar extends React.Component<ResizerProps> {
private onPointerUp = () => {
this.isResizingActive = false;
this.isHoverActive = false;
- window.removeEventListener("pointermove", this.onPointerMove);
- window.removeEventListener("pointerup", this.onPointerUp);
+ window.removeEventListener('pointermove', this.onPointerMove);
+ window.removeEventListener('pointerup', this.onPointerUp);
this._resizeUndo?.end();
this._resizeUndo = undefined;
- }
+ };
render() {
- return <div className="multiColumnResizer"
- style={{
- width: this.props.width,
- backgroundColor: !this.props.isContentActive?.() ? "" : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor)
- }}
- onPointerEnter={action(() => this.isHoverActive = true)}
- onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))}
- >
- <div className={"multiColumnResizer-hdl"} onPointerDown={e => this.registerResizing(e)} />
- </div>;
+ return (
+ <div
+ className="multiColumnResizer"
+ style={{
+ pointerEvents: this.props.isContentActive?.() ? 'all' : 'none',
+ width: this.props.width,
+ backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor),
+ }}
+ onPointerEnter={action(() => (this.isHoverActive = true))}
+ onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))}>
+ <div className={'multiColumnResizer-hdl'} onPointerDown={e => this.registerResizing(e)} />
+ </div>
+ );
}
-
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
index 006ef4df6..5a9d6a82c 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
@@ -1,12 +1,12 @@
-import * as React from "react";
-import { observer } from "mobx-react";
-import { observable, action } from "mobx";
-import { Doc } from "../../../../fields/Doc";
-import { NumCast, StrCast } from "../../../../fields/Types";
-import { DimUnit } from "./CollectionMultirowView";
-import { UndoManager } from "../../../util/UndoManager";
-import { StyleProp } from "../../StyleProvider";
-import { StyleProviderFunc } from "../../nodes/DocumentView";
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { observable, action } from 'mobx';
+import { Doc } from '../../../../fields/Doc';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { DimUnit } from './CollectionMultirowView';
+import { UndoManager } from '../../../util/UndoManager';
+import { StyleProp } from '../../StyleProvider';
+import { StyleProviderFunc } from '../../nodes/DocumentView';
interface ResizerProps {
height: number;
@@ -29,13 +29,13 @@ export default class ResizeBar extends React.Component<ResizerProps> {
private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
e.stopPropagation();
e.preventDefault();
- window.removeEventListener("pointermove", this.onPointerMove);
- window.removeEventListener("pointerup", this.onPointerUp);
- window.addEventListener("pointermove", this.onPointerMove);
- window.addEventListener("pointerup", this.onPointerUp);
+ window.removeEventListener('pointermove', this.onPointerMove);
+ window.removeEventListener('pointerup', this.onPointerUp);
+ window.addEventListener('pointermove', this.onPointerMove);
+ window.addEventListener('pointerup', this.onPointerUp);
this.isResizingActive = true;
- this._resizeUndo = UndoManager.StartBatch("multcol resizing");
- }
+ this._resizeUndo = UndoManager.StartBatch('multcol resizing');
+ };
private onPointerMove = ({ movementY }: PointerEvent) => {
const { toTop: toTop, toBottom: toBottom, columnUnitLength } = this.props;
@@ -45,30 +45,30 @@ export default class ResizeBar extends React.Component<ResizerProps> {
const unitLength = columnUnitLength();
if (unitLength) {
if (toNarrow) {
- const scale = StrCast(toNarrow._dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
+ const scale = StrCast(toNarrow._dimUnit, '*') === DimUnit.Ratio ? unitLength : 1;
toNarrow._dimMagnitude = Math.max(0.05, NumCast(toNarrow._dimMagnitude, 1) - Math.abs(movementY) / scale);
}
if (toWiden) {
- const scale = StrCast(toWiden._dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
+ const scale = StrCast(toWiden._dimUnit, '*') === DimUnit.Ratio ? unitLength : 1;
toWiden._dimMagnitude = Math.max(0.05, NumCast(toWiden._dimMagnitude, 1) + Math.abs(movementY) / scale);
}
}
- }
+ };
private get isActivated() {
const { toTop, toBottom } = this.props;
if (toTop && toBottom) {
- if (StrCast(toTop._dimUnit, "*") === DimUnit.Pixel && StrCast(toBottom._dimUnit, "*") === DimUnit.Pixel) {
+ if (StrCast(toTop._dimUnit, '*') === DimUnit.Pixel && StrCast(toBottom._dimUnit, '*') === DimUnit.Pixel) {
return false;
}
return true;
} else if (toTop) {
- if (StrCast(toTop._dimUnit, "*") === DimUnit.Pixel) {
+ if (StrCast(toTop._dimUnit, '*') === DimUnit.Pixel) {
return false;
}
return true;
} else if (toBottom) {
- if (StrCast(toBottom._dimUnit, "*") === DimUnit.Pixel) {
+ if (StrCast(toBottom._dimUnit, '*') === DimUnit.Pixel) {
return false;
}
return true;
@@ -80,23 +80,25 @@ export default class ResizeBar extends React.Component<ResizerProps> {
private onPointerUp = () => {
this.isResizingActive = false;
this.isHoverActive = false;
- window.removeEventListener("pointermove", this.onPointerMove);
- window.removeEventListener("pointerup", this.onPointerUp);
+ window.removeEventListener('pointermove', this.onPointerMove);
+ window.removeEventListener('pointerup', this.onPointerUp);
this._resizeUndo?.end();
this._resizeUndo = undefined;
- }
+ };
render() {
- return <div className="multiRowResizer"
- style={{
- height: this.props.height,
- backgroundColor: !this.props.isContentActive?.() ? "" : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor)
- }}
- onPointerEnter={action(() => this.isHoverActive = true)}
- onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))}
- >
- <div className={"multiRowResizer-hdl"} onPointerDown={e => this.registerResizing(e)} />
- </div>;
+ return (
+ <div
+ className="multiRowResizer"
+ style={{
+ pointerEvents: this.props.isContentActive?.() ? 'all' : 'none',
+ height: this.props.height,
+ backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor),
+ }}
+ onPointerEnter={action(() => (this.isHoverActive = true))}
+ onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))}>
+ <div className={'multiRowResizer-hdl'} onPointerDown={e => this.registerResizing(e)} />
+ </div>
+ );
}
-
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 52ebb7763..76bd392a5 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -9,12 +9,32 @@
.schema-table {
background-color: $white;
cursor: grab;
+ width: 100%;
.schema-table-content {
overflow: overlay;
scroll-behavior: smooth;
}
+ .schema-add {
+ position: relative;
+ height: 30;
+ display: flex;
+ align-items: center;
+ width: 100%;
+ text-align: right;
+ background: lightgray;
+ .editableView-container-editing {
+ width: 100%;
+ }
+ .editableView-input {
+ width: 100%;
+ float: right;
+ text-align: right;
+ background: yellow;
+ }
+ }
+
.schema-column-menu,
.schema-filter-menu {
background: $light-gray;
@@ -173,6 +193,12 @@
flex-direction: row;
height: 100%;
overflow: auto;
+
+ .schemaSelectionCell {
+ align-self: center;
+ width: 100%;
+ display: flex;
+ }
}
.schema-header-row > .schema-column-header:nth-child(2) > .left {
@@ -185,7 +211,7 @@
overflow-x: hidden;
overflow-y: auto;
padding: 5px;
- display: inline-block;
+ display: inline-flex;
}
.schema-row {
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index babe5c810..f73c037f4 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -63,7 +63,7 @@ export class CollectionSchemaView extends CollectionSubView() {
public static _minColWidth: number = 25;
public static _rowMenuWidth: number = 60;
public static _previewDividerWidth: number = 4;
- public static _newNodeInputHeight: number = 30;
+ public static _newNodeInputHeight: number = 20;
public fieldInfos = new ObservableMap<string, FInfo>();
@observable _menuKeys: string[] = [];
@@ -609,10 +609,10 @@ export class CollectionSchemaView extends CollectionSubView() {
this._menuKeys = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase()));
};
- getFieldFilters = (field: string) => StrListCast(this.Document._childFilters).filter(filter => filter.split(':')[0] == field);
+ getFieldFilters = (field: string) => StrListCast(this.Document._childFilters).filter(filter => filter.split(Doc.FilterSep)[0] == field);
removeFieldFilters = (field: string) => {
- this.getFieldFilters(field).forEach(filter => Doc.setDocFilter(this.Document, field, filter.split(':')[1], 'remove'));
+ this.getFieldFilters(field).forEach(filter => Doc.setDocFilter(this.Document, field, filter.split(Doc.FilterSep)[1], 'remove'));
};
onFilterKeyDown = (e: React.KeyboardEvent) => {
@@ -766,8 +766,8 @@ export class CollectionSchemaView extends CollectionSubView() {
return keyOptions.map(key => {
let bool = false;
if (filters !== undefined) {
- const ind = filters.findIndex(filter => filter.split(':')[1] === key);
- const fields = ind === -1 ? undefined : filters[ind].split(':');
+ const ind = filters.findIndex(filter => filter.split(Doc.FilterSep)[1] === key);
+ const fields = ind === -1 ? undefined : filters[ind].split(Doc.FilterSep);
bool = fields ? fields[2] === 'check' : false;
}
return (
@@ -836,6 +836,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }}></div>
<div
className="schema-table"
+ style={{ width: `calc(100% - ${this.previewWidth + 4}px)` }}
onWheel={e => this.props.isContentActive() && e.stopPropagation()}
ref={r => {
// prevent wheel events from passively propagating up through containers
@@ -869,14 +870,18 @@ export class CollectionSchemaView extends CollectionSubView() {
{this._columnMenuIndex !== undefined && this.renderColumnMenu}
{this._filterColumnIndex !== undefined && this.renderFilterMenu}
<CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} rowHeight={this.rowHeightFunc} setRef={(ref: HTMLDivElement | null) => (this._tableContentRef = ref)} />
- <EditableView
- GetValue={returnEmptyString}
- SetValue={undoable(value => (value ? this.addRow(Docs.Create.TextDocument(value, { title: value, _layout_autoHeight: true })) : false), 'add text doc')}
- placeholder={"Type ':' for commands"}
- contents={'+ New Node'}
- menuCallback={this.menuCallback}
- height={CollectionSchemaView._newNodeInputHeight}
- />
+ {this.layoutDoc.chromeHidden ? null : (
+ <div className="schema-add">
+ <EditableView
+ GetValue={returnEmptyString}
+ SetValue={undoable(value => (value ? this.addRow(Docs.Create.TextDocument(value, { title: value, _layout_autoHeight: true })) : false), 'add text doc')}
+ placeholder={"Type text to create note or ':' to create specific type"}
+ contents={'+ New Node'}
+ menuCallback={this.menuCallback}
+ height={CollectionSchemaView._newNodeInputHeight}
+ />
+ </div>
+ )}
</div>
{this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>}
{this.previewWidth > 0 && (
@@ -967,7 +972,6 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
hideDocumentButtonBar={true}
hideLinkAnchors={true}
layout_fitWidth={returnTrue}
- scriptContext={this}
/>
</div>
);
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index e8e606030..4e418728f 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -16,6 +16,10 @@ import { CollectionSchemaView } from './CollectionSchemaView';
import './CollectionSchemaView.scss';
import { SchemaTableCell } from './SchemaTableCell';
import { Transform } from '../../../util/Transform';
+import { IconButton, Size } from 'browndash-components';
+import { CgClose } from 'react-icons/cg';
+import { FaExternalLinkAlt } from 'react-icons/fa';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
@observer
export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -110,22 +114,40 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
width: CollectionSchemaView._rowMenuWidth,
pointerEvents: !this.props.isContentActive() ? 'none' : undefined,
}}>
- <div
- className="schema-row-button"
- onPointerDown={undoable(e => {
- e.stopPropagation();
- this.props.removeDocument?.(this.rootDoc);
- }, 'Delete Row')}>
- <FontAwesomeIcon icon="times" />
- </div>
- <div
- className="schema-row-button"
- onPointerDown={undoable(e => {
- e.stopPropagation();
- this.props.addDocTab(this.rootDoc, OpenWhere.addRight);
- }, 'Open Doc on Right')}>
- <FontAwesomeIcon icon="external-link-alt" />
- </div>
+ <IconButton
+ tooltip="close"
+ icon={<CgClose size={'16px'} />}
+ size={Size.XSMALL}
+ onPointerDown={e =>
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ undoable(e => {
+ e.stopPropagation();
+ this.props.removeDocument?.(this.rootDoc);
+ }, 'Delete Row')
+ )
+ }
+ />
+ <IconButton
+ tooltip="open preview"
+ icon={<FaExternalLinkAlt />}
+ size={Size.XSMALL}
+ onPointerDown={e =>
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ undoable(e => {
+ e.stopPropagation();
+ this.props.addDocTab(this.rootDoc, OpenWhere.addRight);
+ }, 'Open schema Doc preview')
+ )
+ }
+ />
</div>
<div className="row-cells">
{this.schemaView?.columnKeys?.map((key, index) => (
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 1c9c0de53..9d5b533d1 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -232,7 +232,7 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
const height = this.props.rowHeight() ? this.props.rowHeight() - (this.props.padding || 6) * 2 : undefined;
const width = height ? height * aspect : undefined; // increase the width of the image if necessary to maintain proportionality
- return <img src={this.url} width={width} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />;
+ return <img src={this.url} width={width ? width : undefined} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />;
}
}
@@ -272,7 +272,7 @@ export class SchemaRTFCell extends React.Component<SchemaTableCellProps> {
fieldProps.isContentActive = this.selectedFunc;
return (
<div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}>
- {this.selected ? <FormattedTextBox allowScroll={true} {...fieldProps} DataDoc={this.props.Document} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
+ {this.selected ? <FormattedTextBox {...fieldProps} DataDoc={this.props.Document} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
</div>
);
}
@@ -325,10 +325,34 @@ export class SchemaEnumerationCell extends React.Component<SchemaTableCellProps>
const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
const options = this.props.options?.map(facet => ({ value: facet, label: facet }));
return (
- <div className="schemaSelectionCell" style={{ display: 'flex', color, textDecoration, cursor, pointerEvents }}>
+ <div className="schemaSelectionCell" style={{ color, textDecoration, cursor, pointerEvents }}>
<div style={{ width: '100%' }}>
<Select
styles={{
+ container: base => ({
+ ...base,
+ height: 20,
+ }),
+ control: base => ({
+ ...base,
+ height: 20,
+ minHeight: 20,
+ }),
+ placeholder: base => ({
+ ...base,
+ top: '40%',
+ }),
+ input: base => ({
+ ...base,
+ height: 20,
+ minHeight: 20,
+ margin: 0,
+ }),
+ indicatorsContainer: base => ({
+ ...base,
+ height: 20,
+ transform: 'scale(0.75)',
+ }),
menuPortal: base => ({
...base,
left: 0,
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index 7b2ac5713..ddd99c836 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -71,7 +71,7 @@ $LEFT_MENU_WIDTH: 60px;
$TREE_BULLET_WIDTH: 20px;
$CAROUSEL3D_CENTER_SCALE: 1.3;
-$CAROUSEL3D_SIDE_SCALE: 0.3;
+$CAROUSEL3D_SIDE_SCALE: 0.6;
$CAROUSEL3D_TOP: 15;
:export {
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 38bf1042d..d8ed93bd7 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -85,10 +85,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
});
-ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'viewAllPersist', checkResult?: boolean) {
+ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'viewAllPersist', checkResult?: boolean) {
const selected = SelectionManager.Docs().lastElement();
// prettier-ignore
- const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'viewAllPersist', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
+ const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'viewAllPersist', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
['grid', {
checkResult: (doc:Doc) => BoolCast(doc._freeform_backgroundGrid, false),
setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
@@ -101,6 +101,10 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snapli
checkResult: (doc:Doc) => BoolCast(doc._freeform_fitContentsToBox, false),
setDoc: (doc:Doc) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox,
}],
+ ['center', {
+ checkResult: (doc:Doc) => BoolCast(doc._stacking_alignCenter, false),
+ setDoc: (doc:Doc) => doc._stacking_alignCenter = !doc._stacking_alignCenter,
+ }],
['viewAllPersist', {
checkResult: (doc:Doc) => false,
setDoc: (doc:Doc) => doc.fitContentOnce = true
@@ -182,8 +186,8 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?:
toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]);
// prettier-ignore
const attrs:attrfuncs[] = [
- ['dictation', { checkResult: () => textView?._recording ? true:false,
- toggle: () => textView && runInAction(() => (textView._recording = !textView._recording)) }],
+ ['dictation', { checkResult: () => textView?._recordingDictation ? true:false,
+ toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }],
['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false),
toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
['bold', { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 9bc8f5e8b..bf2a4e1a9 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -69,10 +69,8 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
}
return this.props.sourceDoc;
}
- @action
+
onEdit = (e: React.PointerEvent) => {
- LinkManager.currentLink = this.props.linkDoc === LinkManager.currentLink ? undefined : this.props.linkDoc;
- LinkManager.currentLinkAnchor = this.sourceAnchor;
setupMoveUpEvents(
this,
e,
@@ -90,8 +88,11 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
DocumentViewInternal.addDocTabFunc(trail, OpenWhere.replaceRight);
} else {
SelectionManager.SelectView(this.props.docView, false);
+ LinkManager.currentLink = this.props.linkDoc === LinkManager.currentLink ? undefined : this.props.linkDoc;
+ LinkManager.currentLinkAnchor = LinkManager.currentLink ? this.sourceAnchor : undefined;
+
if ((SettingsManager.propertiesWidth ?? 0) < 100) {
- SettingsManager.propertiesWidth = 250;
+ setTimeout(action(() => (SettingsManager.propertiesWidth = 250)));
}
}
})
@@ -150,7 +151,11 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
className="linkMenu-item"
onPointerEnter={action(e => (this._hover = true))}
onPointerLeave={action(e => (this._hover = false))}
- style={{ background: /*LinkManager.currentLink !== this.props.linkDoc*/ this._hover ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}>
+ style={{
+ fontSize: this._hover ? 'larger' : undefined,
+ fontWeight: this._hover ? 'bold' : undefined,
+ background: LinkManager.currentLink === this.props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ }}>
<div className="linkMenu-item-content expand-two">
<div
ref={this._drag}
@@ -192,7 +197,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
{this.props.linkDoc.linksToAnnotation && Cast(this.props.destinationDoc.data, WebField)?.url.href === this.props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
</p>
</div>
- {!this.props.linkDoc.link_description ? null : <p className="linkMenu-description">{StrCast(this.props.linkDoc.link_description)}</p>}
+ {!this.props.linkDoc.link_description ? null : <p className="linkMenu-description">{StrCast(this.props.linkDoc.link_description).split('\n')[0].substring(0, 50)}</p>}
</div>
<div className="linkMenu-item-buttons">
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 7c409c38c..8d80f1364 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,14 +1,15 @@
import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
import { action, computed, IReactionDisposer, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { DateField } from '../../../fields/DateField';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { Doc } from '../../../fields/Doc';
import { ComputedField } from '../../../fields/ScriptField';
import { Cast, DateCast, NumCast } from '../../../fields/Types';
import { AudioField, nullAudio } from '../../../fields/URLField';
import { emptyFunction, formatTime, returnFalse, setupMoveUpEvents } from '../../../Utils';
-import { DocUtils } from '../../documents/Documents';
+import { Docs, DocUtils } from '../../documents/Documents';
import { Networking } from '../../Network';
import { DragManager } from '../../util/DragManager';
import { LinkManager } from '../../util/LinkManager';
@@ -18,6 +19,7 @@ import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
import './AudioBox.scss';
+import { DocFocusOptions } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { PinProps, PresBox } from './trails';
@@ -66,7 +68,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
_stream: MediaStream | undefined; // passed to MediaRecorder, records device input audio
_play: any = null; // timeout for playback
- @observable _stackedTimeline: any; // CollectionStackedTimeline ref
+ @observable _stackedTimeline: CollectionStackedTimeline | null | undefined; // CollectionStackedTimeline ref
@observable _finished: boolean = false; // has playback reached end of clip
@observable _volume: number = 1;
@observable _muted: boolean = false;
@@ -134,16 +136,19 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- const anchor =
- CollectionStackedTimeline.createAnchor(
- this.rootDoc,
- this.dataDoc,
- this.annotationKey,
- this._ele?.currentTime || Cast(this.props.Document._layout_currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined),
- undefined,
- undefined,
- addAsAnnotation
- ) || this.rootDoc;
+ const timecode = Cast(this.layoutDoc._layout_currentTimecode, 'number', null);
+ const anchor = addAsAnnotation
+ ? CollectionStackedTimeline.createAnchor(
+ this.rootDoc,
+ this.dataDoc,
+ this.annotationKey,
+ this._ele?.currentTime || Cast(this.props.Document._layout_currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined),
+ undefined,
+ undefined,
+ addAsAnnotation
+ ) || this.rootDoc
+ : Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.rootDoc });
+
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true } }, this.rootDoc);
return anchor;
};
@@ -418,7 +423,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
};
// plays link
- playLink = (link: Doc) => {
+ playLink = (link: Doc, options: DocFocusOptions) => {
if (link.annotationOn === this.rootDoc) {
if (!this.layoutDoc.dontAutoPlayFollowedLinks) {
this.playFrom(this.timeline?.anchorStart(link) || 0, this.timeline?.anchorEnd(link));
@@ -473,8 +478,34 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
};
// for trim button, double click displays full clip, single displays curr trim bounds
+ onResetPointerDown = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ this.timeline &&
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ returnFalse,
+ action(e => {
+ if (this.timeline?.IsTrimming !== TrimScope.None) {
+ this.timeline?.CancelTrimming();
+ } else {
+ this.beginEndtime = this.timeline?.trimEnd;
+ this.beginStarttime = this.timeline?.trimStart;
+ this.startTrim(TrimScope.All);
+ }
+ })
+ );
+ };
+
+ beginEndtime: number | undefined;
+ beginStarttime: number | undefined;
+
+ // for trim button, double click displays full clip, single displays curr trim bounds
onClipPointerDown = (e: React.PointerEvent) => {
e.stopPropagation();
+ this.beginEndtime = this.timeline?.trimEnd;
+ this.beginStarttime = this.timeline?.trimStart;
this.timeline &&
setupMoveUpEvents(
this,
@@ -525,7 +556,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
r,
(e, de) => {
const [xp, yp] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
- de.complete.docDragData && this.timeline.internalDocDrop(e, de, de.complete.docDragData, xp);
+ de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp);
},
this.layoutDoc,
undefined
@@ -587,9 +618,22 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
</div>
{!this.miniPlayer && (
- <div className="audiobox-button" title={this.timeline?.IsTrimming !== TrimScope.None ? 'finish' : 'trim'} onPointerDown={this.onClipPointerDown}>
- <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'check' : 'cut'} size={'1x'} />
- </div>
+ <>
+ <Tooltip title={<>trim audio</>}>
+ <div className="audiobox-button" onPointerDown={this.onClipPointerDown}>
+ <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'check' : 'cut'} size={'1x'} />
+ </div>
+ </Tooltip>
+ {this.timeline?.IsTrimming == TrimScope.None && !NumCast(this.layoutDoc.clipStart) && NumCast(this.layoutDoc.clipEnd) === this.rawDuration ? (
+ <></>
+ ) : (
+ <Tooltip title={<>{this.timeline?.IsTrimming !== TrimScope.None ? 'Cancel trimming' : 'Edit original timeline'}</>}>
+ <div className="audiobox-button" onPointerDown={this.onResetPointerDown}>
+ <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'cancel' : 'arrows-left-right'} size={'1x'} />
+ </div>
+ </Tooltip>
+ )}
+ </>
)}
</div>
<div className="controls-right">
@@ -654,7 +698,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@computed get renderTimeline() {
return (
<CollectionStackedTimeline
- ref={action((r: any) => (this._stackedTimeline = r))}
+ ref={action((r: CollectionStackedTimeline | null) => (this._stackedTimeline = r))}
{...this.props}
CollectionFreeFormDocumentView={undefined}
dataFieldKey={this.fieldKey}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 6710cee63..4a438f826 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -40,7 +40,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
{ key: 'opacity', val: 1 },
{ key: '_currentFrame' },
{ key: 'freeform_scale', val: 1 },
- { key: 'freeform_scale', val: 1 },
{ key: 'freeform_panX' },
{ key: 'freeform_panY' },
]; // fields that are configured to be animatable using animation frames
diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss
index a12f1c12b..39c864b2b 100644
--- a/src/client/views/nodes/ComparisonBox.scss
+++ b/src/client/views/nodes/ComparisonBox.scss
@@ -60,6 +60,8 @@
opacity: 0.8;
pointer-events: all;
cursor: pointer;
+ width: 15px;
+ height: 15px;
}
.clear-button.before {
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index a334e75f1..b09fcd882 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -109,12 +109,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
};
@undoBatch
- clearDoc = (e: React.MouseEvent, fieldKey: string) => {
- e.stopPropagation; // prevent click event action (slider movement) in registerSliding
- delete this.dataDoc[fieldKey];
- };
+ clearDoc = (fieldKey: string) => delete this.dataDoc[fieldKey];
+
moveDoc = (doc: Doc, addDocument: (document: Doc | Doc[]) => boolean, which: string) => this.remDoc(doc, which) && addDocument(doc);
addDoc = (doc: Doc, which: string) => {
+ if (this.dataDoc[which]) return false;
this.dataDoc[which] = doc;
return true;
};
@@ -128,6 +127,24 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
whenChildContentsActiveChanged = action((isActive: boolean) => (this._isAnyChildContentActive = isActive));
+ closeDown = (e: React.PointerEvent, which: string) => {
+ setupMoveUpEvents(
+ this,
+ e,
+ e => {
+ const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], 'move');
+ de.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
+ this.clearDoc(which);
+ return addDocument(doc);
+ };
+ de.canEmbed = true;
+ DragManager.StartDocumentDrag([this._closeRef.current!], de, e.clientX, e.clientY);
+ return true;
+ },
+ emptyFunction,
+ e => this.clearDoc(which)
+ );
+ };
docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
if (property === StyleProp.PointerEvents) return 'none';
return this.props.styleProvider?.(doc, props, property);
@@ -136,13 +153,15 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);
+ _closeRef = React.createRef<HTMLDivElement>();
render() {
const clearButton = (which: string) => {
return (
<div
+ ref={this._closeRef}
className={`clear-button ${which}`}
- onPointerDown={e => e.stopPropagation()} // prevent triggering slider movement in registerSliding
- onClick={e => this.clearDoc(e, which)}>
+ onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding
+ >
<FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" />
</div>
);
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss
index a69881b7c..430446c06 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.scss
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss
@@ -1,12 +1,15 @@
.dataviz {
- overflow: scroll;
+ overflow: auto;
height: 100%;
width: 100%;
- .datatype-button{
+ .datatype-button {
display: flex;
flex-direction: row;
}
+ .button-container {
+ pointer-events: unset;
+ }
}
.start-message {
margin: 10px;
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index 50dfe7f05..3072d0907 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -6,10 +6,10 @@
margin-top: 10px;
overflow-y: visible;
- .graph{
+ .graph {
overflow: visible;
}
- .graph-title{
+ .graph-title {
align-items: center;
font-size: larger;
display: flex;
@@ -29,7 +29,7 @@
position: relative;
margin-bottom: -35px;
}
- .selected-data{
+ .selected-data {
align-items: center;
text-align: center;
display: flex;
@@ -44,10 +44,10 @@
stroke-width: 2px;
}
}
-
- .histogram-bar{
+
+ .histogram-bar {
outline: thin solid black;
- &.hover{
+ &.hover {
outline: 3px solid black;
outline-offset: -3px;
}
@@ -91,13 +91,22 @@
.tableBox {
display: flex;
flex-direction: column;
+ height: calc(100% - 40px); // bcz: hack 40px is the size of the button rows
+ .table {
+ height: 100%;
+ }
}
-.table-container{
+.table-container {
overflow: scroll;
margin: 5px;
margin-left: 25px;
margin-right: 10px;
margin-bottom: 0;
+ tr td {
+ height: 40px !important; // bcz: hack. you can't set a <tr> height directly, but you can set the height of all of it's <td>s. So this is the height of a tableBox row.
+ padding: 0 !important;
+ vertical-align: middle !important;
+ }
}
.selectAll-buttons {
display: flex;
@@ -106,4 +115,4 @@
margin-top: 5px;
margin-right: 10px;
float: right;
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.scss b/src/client/views/nodes/DataVizBox/components/TableBox.scss
deleted file mode 100644
index 1264d6a46..000000000
--- a/src/client/views/nodes/DataVizBox/components/TableBox.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-.table {
- margin-top: 10px;
- margin-bottom: 10px;
- margin-left: 10px;
- margin-right: 10px;
-}
-
-.table-row {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
- padding: 5px;
- border-bottom: 1px solid #ccc;
-}
-
-.table-container {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
-} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 8ac9d6804..e1de2fa76 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -41,7 +41,6 @@ import { PhysicsSimulationBox } from './PhysicsBox/PhysicsSimulationBox';
import { RecordingBox } from './RecordingBox';
import { ScreenshotBox } from './ScreenshotBox';
import { ScriptingBox } from './ScriptingBox';
-import { SliderBox } from './SliderBox';
import { PresBox } from './trails/PresBox';
import { VideoBox } from './VideoBox';
import { WebBox } from './WebBox';
@@ -240,7 +239,6 @@ export class DocumentContentsView extends React.Component<
FontIconBox,
LabelBox,
EquationBox,
- SliderBox,
FieldView,
CollectionFreeFormView,
CollectionDockingView,
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index 6e2ed72b8..bccbd66e8 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -9,6 +9,7 @@ import { action, observable } from 'mobx';
import { Id } from '../../../fields/FieldSymbols';
import { factory } from 'typescript';
import { LightboxView } from '../LightboxView';
+import { SettingsManager } from '../../util/SettingsManager';
@observer
export class DocumentIcon extends React.Component<{ view: DocumentView; index: number }> {
@@ -29,6 +30,7 @@ export class DocumentIcon extends React.Component<{ view: DocumentView; index: n
pointerEvents: 'all',
opacity: this._hovered ? 0.3 : 1,
position: 'absolute',
+ background: SettingsManager.userBackgroundColor,
transform: `translate(${(left + right) / 2}px, ${top}px)`,
}}>
<Tooltip title={<>{this.props.view.rootDoc.title}</>}>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 74eee49e0..e4fc6c4a2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, Animation, DocData, Width } from '../../../fields/DocSymbols';
+import { AclPrivate, Animation, AudioPlay, DocData, Width } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
@@ -100,6 +100,7 @@ export interface DocFocusOptions {
effect?: Doc; // animation effect for focus
noSelect?: boolean; // whether target should be selected after focusing
playAudio?: boolean; // whether to play audio annotation on focus
+ playMedia?: boolean; // whether to play start target videos
openLocation?: OpenWhere; // where to open a missing document
zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
toggleTarget?: boolean; // whether to toggle target on and off
@@ -120,6 +121,7 @@ export interface DocComponentView {
reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views
select?: (ctrlKey: boolean, shiftKey: boolean) => void;
+ focus?: (textAnchor: Doc, options: DocFocusOptions) => Opt<number>;
menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
isAnyChildContentActive?: () => boolean; // is any child content of the document active
onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
@@ -392,18 +394,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
dragData.treeViewDoc = this.props.treeViewDoc;
dragData.removeDocument = this.props.removeDocument;
dragData.moveDocument = this.props.moveDocument;
+ dragData.draggedViews = [this.props.DocumentView()];
dragData.canEmbed = this.rootDoc.dragAction ?? this.props.dragAction ? true : false;
- const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
- ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
DragManager.StartDocumentDrag(
selected.map(dv => dv.docView!._mainCont.current!),
dragData,
x,
y,
- { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this.props.dontHideOnDrag) },
- () => setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))
+ { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this.props.dontHideOnDrag) }
); // this needs to happen after the drop event is processed.
- ffview?.setupDragLines(false);
}
}
@@ -493,11 +492,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
const sendToBack = e.altKey;
this._singleClickFunc =
- clickFunc ??
- (() =>
- sendToBack
- ? this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.bringToFront(this.rootDoc, true)
- : this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? this.props.select(e.ctrlKey || e.metaKey || e.shiftKey));
+ // prettier-ignore
+ clickFunc ?? (() => (sendToBack ? this.props.DocumentView().props.bringToFront(this.rootDoc, true) :
+ this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ??
+ this.props.select(e.ctrlKey || e.metaKey || e.shiftKey)));
const waitFordblclick = this.props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
@@ -629,7 +627,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
if (linkdrag) {
linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor();
- if (linkdrag.linkSourceDoc) {
+ if (linkdrag.linkSourceDoc && linkdrag.linkSourceDoc !== this.rootDoc) {
if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) {
de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined);
}
@@ -700,7 +698,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
const cm = ContextMenu.Instance;
- if (!cm || (e as any)?.nativeEvent?.SchemaHandled) return;
+ if (!cm || (e as any)?.nativeEvent?.SchemaHandled || DocumentView.ExploreMode) return;
if (e && !(e.nativeEvent as any).dash) {
const onDisplay = () => {
@@ -761,10 +759,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
- onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
+ !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
!existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
} else if (LinkManager.Links(this.Document).length) {
- onClicks.push({ description: 'Select on Click', event: () => this.noOnClick(), icon: 'link' });
+ onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' });
onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
!existingOnClick && cm.addItem({ description: 'OnClick...', subitems: onClicks, icon: 'mouse-pointer' });
}
@@ -798,7 +796,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
!more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' });
}
const constantItems: ContextMenuProps[] = [];
- if (!Doc.IsSystem(this.rootDoc)) {
+ if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._type_collection !== CollectionViewType.Docking) {
constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
(this.rootDoc._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' });
if (this.props.removeDocument && Doc.ActiveDashboard !== this.props.Document) {
@@ -965,6 +963,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
case StyleProp.ShowTitle: return '';
case StyleProp.PointerEvents: return 'none';
case StyleProp.Highlighting: return undefined;
+ case StyleProp.Opacity: {
+ const filtered = DocUtils.FilterDocs(this.directLinks, this.props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
+ return filtered.some(link => link._link_displayArrow) ? 0 : undefined;
+ }
}
return this.props.styleProvider?.(doc, props, property);
};
@@ -1052,7 +1054,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
}
};
- runInAction(() => (dataDoc.audioAnnoState = 'recording'));
+ //runInAction(() => (dataDoc.audioAnnoState = 'recording'));
recorder.start();
const stopFunc = () => {
recorder.stop();
@@ -1069,23 +1071,30 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped';
const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null);
const anno = audioAnnos?.lastElement();
- if (anno instanceof AudioField && audioAnnoState === 'stopped') {
- new Howl({
- src: [anno.url.href],
- format: ['mp3'],
- autoplay: true,
- loop: false,
- volume: 0.5,
- onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')),
- });
- this.dataDoc.audioAnnoState = 'playing';
+ if (anno instanceof AudioField) {
+ switch (audioAnnoState) {
+ case 'stopped':
+ this.dataDoc[AudioPlay] = new Howl({
+ src: [anno.url.href],
+ format: ['mp3'],
+ autoplay: true,
+ loop: false,
+ volume: 0.5,
+ onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')),
+ });
+ this.dataDoc.audioAnnoState = 'playing';
+ break;
+ case 'playing':
+ this.dataDoc[AudioPlay]?.stop();
+ this.dataDoc.audioAnnoState = 'stopped';
+ break;
+ }
}
};
captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption');
@computed get innards() {
TraceMobx();
- const ffscale = () => this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1;
const showTitle = this.layout_showTitle?.split(':')[0];
const showTitleHover = this.layout_showTitle?.includes(':hover');
const captionView = !this.layout_showCaption ? null : (
@@ -1093,8 +1102,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
className="documentView-captionWrapper"
style={{
pointerEvents: this.rootDoc.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
- minWidth: 50 * ffscale(),
- maxHeight: `max(100%, ${20 * ffscale()}px)`,
background: StrCast(this.layoutDoc._backgroundColor, 'rgba(0,0,0,0.2)'),
color: lightOrDark(StrCast(this.layoutDoc._backgroundColor, 'black')),
}}>
@@ -1103,7 +1110,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
yPadding={10}
xPadding={10}
fieldKey={this.layout_showCaption}
- fontSize={12 * Math.max(1, (2 * ffscale()) / 3)}
styleProvider={this.captionStyleProvider}
dontRegisterView={true}
noSidebar={true}
@@ -1114,7 +1120,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
</div>
);
const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc;
- const background = StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userVariantColor));
+ const background = StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor));
const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
const titleView = !showTitle ? null : (
<div
@@ -1124,7 +1130,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
position: this.headerMargin ? 'relative' : 'absolute',
height: this.titleHeight,
width: !this.headerMargin ? `calc(${sidebarWidthPercent || 100}% - 18px)` : (sidebarWidthPercent || 100) + '%', // leave room for annotation button
- color: lightOrDark(background),
+ color: background === 'transparent' ? SettingsManager.userColor : lightOrDark(background),
background,
pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
}}>
@@ -1138,14 +1144,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
display={'block'}
fontSize={10}
GetValue={() => {
- this.props.select(false);
return showTitle.split(';').length === 1 ? showTitle + '=' + Field.toString(targetDoc[showTitle.split(';')[0]] as any as Field) : '#' + showTitle;
}}
SetValue={undoBatch((input: string) => {
if (input?.startsWith('#')) {
- if (this.props.layout_showTitle) {
+ if (this.rootDoc.layout_showTitle) {
this.rootDoc._layout_showTitle = input?.substring(1) ? input.substring(1) : undefined;
- } else {
+ } else if (!this.props.layout_showTitle) {
Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'author_date';
}
} else {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 4ebf22ddf..f014f842e 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -34,7 +34,6 @@ export interface FieldViewProps extends DocumentViewSharedProps {
backgroundColor?: string;
treeViewDoc?: Doc;
color?: string;
- fontSize?: number;
height?: number;
width?: number;
noSidebar?: boolean;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 64f289cf7..d2e1293da 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -6,8 +6,8 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
import { ScriptField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { Utils } from '../../../../Utils';
+import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { SelectionManager } from '../../../util/SelectionManager';
import { SettingsManager } from '../../../util/SettingsManager';
@@ -19,7 +19,6 @@ import { SelectedDocView } from '../../selectedDoc';
import { StyleProp } from '../../StyleProvider';
import { OpenWhere } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
-import { RichTextMenu } from '../formattedText/RichTextMenu';
import './FontIconBox.scss';
import TrailsIcon from './TrailsIcon';
@@ -176,86 +175,88 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
);
}
+ dropdownItemDown = (e: React.PointerEvent, value: string | number) => {
+ setupMoveUpEvents(
+ this,
+ e,
+ (e: PointerEvent) => {
+ return ScriptCast(this.rootDoc.onDragScript)?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: { doc: value, e } }).result;
+ },
+ emptyFunction,
+ emptyFunction
+ );
+ return false;
+ };
+
/**
* Dropdown list
*/
@computed get dropdownListButton() {
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
const script = ScriptCast(this.rootDoc.script);
let noviceList: string[] = [];
let text: string | undefined;
- let dropdown = true;
let getStyle: (val: string) => any = () => {};
let icon: IconProp = 'caret-down';
- let isViewDropdown: boolean = script?.script.originalScript.startsWith('setView');
- try {
- if (isViewDropdown) {
- const selectedDocs: Doc[] = SelectionManager.Docs();
- const selected = SelectionManager.Docs().lastElement();
- if (selected) {
- if (StrCast(selected.type) === DocumentType.COL) {
- text = StrCast(selected._type_collection);
+ const isViewDropdown = script?.script.originalScript.startsWith('setView');
+ if (isViewDropdown) {
+ const selected = SelectionManager.Docs();
+ if (selected.lastElement()) {
+ if (StrCast(selected.lastElement().type) === DocumentType.COL) {
+ text = StrCast(selected.lastElement()._type_collection);
+ } else {
+ if (selected.length > 1) {
+ text = selected.length + ' selected';
} else {
- dropdown = false;
- if (selectedDocs.length > 1) {
- text = selectedDocs.length + ' selected';
- } else {
- text = Utils.cleanDocumentType(StrCast(selected.type) as DocumentType);
- icon = Doc.toIcon(selected);
- }
- return (
- <Popup
- icon={<FontAwesomeIcon size={'1x'} icon={icon} />}
- text={text}
- type={Type.TERT}
- color={SettingsManager.userColor}
- background={SettingsManager.userVariantColor}
- popup={<SelectedDocView selectedDocs={selectedDocs} />}
- fillWidth
- />
- );
+ text = Utils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType);
+ icon = Doc.toIcon(selected.lastElement());
}
- } else {
- dropdown = false;
- return <Button text="None Selected" type={Type.TERT} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} fillWidth inactive />;
+ return (
+ <Popup
+ icon={<FontAwesomeIcon size={'1x'} icon={icon} />}
+ text={text}
+ type={Type.TERT}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
+ popup={<SelectedDocView selectedDocs={selected} />}
+ fillWidth
+ />
+ );
}
- noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
} else {
- text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
- getStyle = (val: string) => {
- return { fontFamily: val };
- };
+ return <Button text="None Selected" type={Type.TERT} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} fillWidth inactive />;
}
- } catch (e) {
- console.log(e);
+ noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Carousel3D, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
+ } else {
+ text = script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result;
+ // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
+ getStyle = (val: string) => ({ fontFamily: val });
}
// Get items to place into the list
const list: IListItemProps[] = this.buttonList
.filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value))
.map(value => ({
- text: value.charAt(0).toUpperCase() + value.slice(1),
+ text: typeof value === 'string' ? value.charAt(0).toUpperCase() + value.slice(1) : StrCast(DocCast(value)?.title),
val: value,
style: getStyle(value),
- onClick: undoable(() => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value }), value),
// shortcut: '#',
}));
return (
- <div style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}>
- <Dropdown
- selectedVal={text}
- setSelectedVal={undoable(val => script.script.run({ this: this.layoutDoc, self: this.rootDoc, val }), `dropdown select ${this.label}`)}
- color={SettingsManager.userColor}
- background={isViewDropdown ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
- type={Type.TERT}
- dropdownType={DropdownType.SELECT}
- items={list}
- tooltip={this.label}
- fillWidth
- />
- </div>
+ <Dropdown
+ selectedVal={text}
+ setSelectedVal={undoable(value => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value }), `dropdown select ${this.label}`)}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
+ type={Type.TERT}
+ closeOnSelect={false}
+ dropdownType={DropdownType.SELECT}
+ onItemDown={this.dropdownItemDown}
+ items={list}
+ tooltip={this.label}
+ fillWidth
+ />
);
}
@@ -343,7 +344,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
toggleStatus={toggleStatus}
text={buttonText}
color={color}
- background={SettingsManager.userBackgroundColor}
+ //background={SettingsManager.userBackgroundColor}
icon={this.Icon(color)!}
label={this.label}
onPointerDown={() => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: !toggleStatus, _readOnly_: false })}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f5c6a9273..1ab120af5 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -85,8 +85,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
};
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
+ const visibleAnchor = this._getAnchor?.(this._savedAnnotations, false); // use marquee anchor, otherwise, save zoom/pan as anchor
const anchor =
- this._getAnchor?.(this._savedAnnotations, false) ?? // use marquee anchor, otherwise, save zoom/pan as anchor
+ visibleAnchor ??
Docs.Create.ConfigDocument({
title: 'ImgAnchor:' + this.rootDoc.title,
config_panX: NumCast(this.layoutDoc._freeform_panX),
@@ -96,8 +97,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
});
if (anchor) {
if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
- /* addAsAnnotation &&*/ this.addDocument(anchor);
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: true } }, this.rootDoc);
+ addAsAnnotation && this.addDocument(anchor);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: visibleAnchor ? false : true } }, this.rootDoc);
return anchor;
}
return this.rootDoc;
@@ -230,13 +231,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
croppingProto['data_nativeWidth'] = anchw;
croppingProto['data_nativeHeight'] = anchh;
croppingProto.freeform_scale = viewScale;
- croppingProto.freeform_scaleMin = viewScale;
+ croppingProto.freeform_scale_min = viewScale;
croppingProto.freeform_panX = anchx / viewScale;
croppingProto.freeform_panY = anchy / viewScale;
- croppingProto.freeform_panXMin = anchx / viewScale;
- croppingProto.freeform_panXMax = anchw / viewScale;
- croppingProto.freeform_panYMin = anchy / viewScale;
- croppingProto.freeform_panYMax = anchh / viewScale;
+ croppingProto.freeform_panX_min = anchx / viewScale;
+ croppingProto.freeform_panX_max = anchw / viewScale;
+ croppingProto.freeform_panY_min = anchy / viewScale;
+ croppingProto.freeform_panY_max = anchh / viewScale;
if (addCrop) {
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
cropping.x = NumCast(this.rootDoc.x) + this.rootDoc[Width]();
@@ -502,7 +503,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._marqueeing = undefined;
this.props.select(false);
};
- focus = (anchor: Doc, options: DocFocusOptions) => this._ffref.current?.focus(anchor, options);
+ focus = (anchor: Doc, options: DocFocusOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));
_ffref = React.createRef<CollectionFreeFormView>();
savedAnnotations = () => this._savedAnnotations;
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index b0d041bdd..f22cb195f 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -111,7 +111,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
X
</button>
<input className="keyValuePair-td-key-check" type="checkbox" style={hover} onChange={this.handleCheck} ref={this.checkbox} />
- <Tooltip title={Object.entries(new DocumentOptions()).find((pair: [string, FInfo]) => pair[0].replace(/^_/, '') === props.fieldKey)?.[1].description}>
+ <Tooltip title={Object.entries(new DocumentOptions()).find((pair: [string, FInfo]) => pair[0].replace(/^_/, '') === props.fieldKey)?.[1].description ?? ''}>
<div className="keyValuePair-keyField" style={{ marginLeft: 20 * (props.fieldKey.match(/_/g)?.length || 0), color: keyStyle }}>
{'('.repeat(parenCount)}
{props.fieldKey}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 42c8ea6a4..198cbe851 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -19,6 +19,7 @@ import { Transform } from '../../util/Transform';
import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView';
import './LinkDocPreview.scss';
import React = require('react');
+import { DocumentManager } from '../../util/DocumentManager';
interface LinkDocPreviewProps {
linkDoc?: Doc;
@@ -176,7 +177,12 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
const webDoc =
Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this.props.hrefs[0]).keys()).lastElement() ??
Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
- this.props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
+ DocumentManager.Instance.showDocument(webDoc, {
+ openLocation: OpenWhere.lightbox,
+ willPan: true,
+ zoomTime: 500,
+ });
+ //this.props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
}
};
@@ -198,7 +204,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
};
@computed get previewHeader() {
return !this._linkDoc || !this._markerTargetDoc || !this._targetDoc || !this._linkSrc ? null : (
- <div className="linkDocPreview-info">
+ <div className="linkDocPreview-info" style={{ background: SettingsManager.userBackgroundColor }}>
<div className="linkDocPreview-buttonBar" style={{ float: 'left' }}>
<Tooltip title={<div className="dash-tooltip">Edit Link</div>} placement="top">
<div className="linkDocPreview-button" onPointerDown={this.editLink}>
@@ -296,7 +302,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
className="linkDocPreview"
ref={this._linkDocRef}
onPointerDown={this.followLinkPointerDown}
- style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}>
+ style={{ borderColor: SettingsManager.userColor, left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}>
{this.docPreview}
</div>
);
diff --git a/src/client/views/nodes/LoadingBox.scss b/src/client/views/nodes/LoadingBox.scss
index d4a7e18f2..cabd4de05 100644
--- a/src/client/views/nodes/LoadingBox.scss
+++ b/src/client/views/nodes/LoadingBox.scss
@@ -5,34 +5,28 @@
justify-content: center;
background-color: #fdfdfd;
height: 100%;
+ width: 100%;
align-items: center;
- .textContainer,
- .text {
- overflow: hidden;
+ .loadingBox-textContainer,
+ .loadingBox-title {
text-overflow: ellipsis;
max-width: 80%;
text-align: center;
- display: flex;
- flex-direction: column;
+ display: block;
gap: 8px;
align-items: center;
+ white-space: normal;
+ word-break: all;
+ .loadingBox-headerText {
+ text-align: center;
+ font-weight: bold;
+ height: auto;
+ width: 100%;
+ }
+ }
+ .loadingBox-spinner {
+ position: absolute;
+ top: 0;
+ left: 0;
}
-}
-
-.textContainer {
- margin: 5px;
-}
-
-.textContainer {
- justify-content: center;
- align-content: center;
-}
-
-.headerText {
- text-align: center;
- font-weight: bold;
-}
-
-.spinner {
- text-align: center;
}
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index 0b02626e9..bdc074e0c 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -46,7 +46,7 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const updateFunc = async () => {
const result = await Networking.QueryYoutubeProgress(StrCast(this.rootDoc[Id])); // We use the guid of the overwriteDoc to track file uploads.
runInAction(() => (this.progress = result.progress));
- this._timer = setTimeout(updateFunc, 1000);
+ !this.rootDoc.loadingError && (this._timer = setTimeout(updateFunc, 1000));
};
this._timer = setTimeout(updateFunc, 1000);
}
@@ -58,10 +58,14 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
render() {
return (
<div className="loadingBoxContainer" style={{ background: !this.rootDoc.loadingError ? '' : 'red' }}>
- <div className="textContainer">
- <p className="headerText">{StrCast(this.rootDoc.loadingError, 'Loading ' + (this.progress.replace('[download]', '') || '(can take several minutes)'))}</p>
- <span className="text">{StrCast(this.rootDoc.title)}</span>
- {this.rootDoc.loadingError ? null : <ReactLoading type={'spinningBubbles'} color={'blue'} height={100} width={100} />}
+ <div className="loadingBox-textContainer">
+ <span className="loadingBox-title">{StrCast(this.rootDoc.title)}</span>
+ <p className="loadingBox-headerText">{StrCast(this.rootDoc.loadingError, 'Loading ' + (this.progress.replace('[download]', '') || '(can take several minutes)'))}</p>
+ {this.rootDoc.loadingError ? null : (
+ <div className="loadingBox-spinner">
+ <ReactLoading type="spinningBubbles" color="blue" height={100} width={100} />
+ </div>
+ )}
</div>
</div>
);
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index f731763af..7af4d9b59 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -1,43 +1,32 @@
import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
+import { IReactionDisposer, ObservableMap, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import { ColorState } from 'react-color';
import { Doc, Opt } from '../../../../fields/Doc';
-import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from '../../../../Utils';
+import { returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../../Utils';
import { SelectionManager } from '../../../util/SelectionManager';
-import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu"
-import { LinkPopup } from '../../linking/LinkPopup';
-import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
// import { GPTPopup, GPTPopupMode } from './../../GPTPopup/GPTPopup';
-import { EditorView } from 'prosemirror-view';
+import { IconButton } from 'browndash-components';
+import { SettingsManager } from '../../../util/SettingsManager';
import './MapAnchorMenu.scss';
-import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { StrCast } from '../../../../fields/Types';
-import { DocumentType } from '../../../documents/DocumentTypes';
@observer
export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
static Instance: MapAnchorMenu;
private _disposer: IReactionDisposer | undefined;
- private _disposer2: IReactionDisposer | undefined;
- private _commentCont = React.createRef<HTMLButtonElement>();
-
-
-
+ private _commentRef = React.createRef<HTMLDivElement>();
public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
public Center: () => void = unimplementedFunction;
- // public OnClick: (e: PointerEvent) => void = unimplementedFunction;
+ public OnClick: (e: PointerEvent) => void = unimplementedFunction;
// public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
- // public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
- // public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
+ public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
public Delete: () => void = unimplementedFunction;
- public LinkNote: () => void = unimplementedFunction;
// public MakeTargetToggle: () => void = unimplementedFunction;
// public ShowTargetTrail: () => void = unimplementedFunction;
public IsTargetToggler: () => boolean = returnFalse;
@@ -54,23 +43,12 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
componentWillUnmount() {
this._disposer?.();
- this._disposer2?.();
}
componentDidMount() {
- this._disposer2 = reaction(
- () => this._opacity,
- opacity => {
- if (!opacity) {
- }
- },
- { fireImmediately: true }
- );
this._disposer = reaction(
() => SelectionManager.Views().slice(),
- selected => {
- MapAnchorMenu.Instance.fadeOut(true);
- }
+ selected => MapAnchorMenu.Instance.fadeOut(true)
);
}
// audioDown = (e: React.PointerEvent) => {
@@ -89,42 +67,54 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
// e => this.OnCrop?.(e)
// );
// };
-
+ notePointerDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(
+ this,
+ e,
+ (e: PointerEvent) => {
+ this.StartDrag(e, this._commentRef.current!);
+ return true;
+ },
+ returnFalse,
+ e => this.OnClick(e)
+ );
+ };
static top = React.createRef<HTMLDivElement>();
// public get Top(){
// return this.top
// }
-
render() {
- const buttons =(
- <>
- {(
- <IconButton
- tooltip="Delete Pin" //
- onPointerDown={this.Delete}
- icon={<FontAwesomeIcon icon="trash-alt" />}
- color={StrCast(Doc.UserDoc().userColor)}
- />
- )}
- {(
+ const buttons = (
+ <>
+ {
+ <IconButton
+ tooltip="Delete Pin" //
+ onPointerDown={this.Delete}
+ icon={<FontAwesomeIcon icon="trash-alt" />}
+ color={SettingsManager.userColor}
+ />
+ }
+ {
+ <div ref={this._commentRef}>
<IconButton
tooltip="Link Note to Pin" //
- onPointerDown={this.LinkNote}
+ onPointerDown={this.notePointerDown}
icon={<FontAwesomeIcon icon="sticky-note" />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
- )}
- {(
- <IconButton
- tooltip="Center on pin" //
- onPointerDown={this.Center}
- icon={<FontAwesomeIcon icon="compress-arrows-alt" />}
- color={StrCast(Doc.UserDoc().userColor)}
- />
- )}
- {/* {this.IsTargetToggler !== returnFalse && (
+ </div>
+ }
+ {
+ <IconButton
+ tooltip="Center on pin" //
+ onPointerDown={this.Center}
+ icon={<FontAwesomeIcon icon="compress-arrows-alt" />}
+ color={SettingsManager.userColor}
+ />
+ }
+ {/* {this.IsTargetToggler !== returnFalse && (
<Toggle
tooltip={'Make target visibility toggle on click'}
type={Type.PRIM}
@@ -132,15 +122,16 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
toggleStatus={this.IsTargetToggler()}
onClick={this.MakeTargetToggle}
icon={<FontAwesomeIcon icon="thumbtack" />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
)} */}
- </>
- );
+ </>
+ );
- return this.getElement(<div ref={MapAnchorMenu.top} style={{width:"100%", display:"flex"}}>
- {buttons}
- </div>
+ return this.getElement(
+ <div ref={MapAnchorMenu.top} style={{ width: '100%', display: 'flex' }}>
+ {buttons}
+ </div>
);
}
}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index f0106dbbb..65c138975 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -6,9 +6,10 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
import { DocCss, Highlight, Width } from '../../../../fields/DocSymbols';
+import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
import { Docs, DocUtils } from '../../../documents/Documents';
import { DocumentType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
@@ -24,6 +25,7 @@ import { MarqueeAnnotator } from '../../MarqueeAnnotator';
import { SidebarAnnos } from '../../SidebarAnnos';
import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
+import { FormattedTextBox } from '../formattedText/FormattedTextBox';
import { PinProps, PresBox } from '../trails';
import { MapAnchorMenu } from './MapAnchorMenu';
import './MapBox.scss';
@@ -70,7 +72,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
private _sidebarRef = React.createRef<SidebarAnnos>();
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [key: string]: IReactionDisposer } = {};
- private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
+ private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
@observable private _marqueeing: number[] | undefined;
@observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@@ -106,6 +108,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
componentWillUnmount(): void {
this._unmounting = true;
this.deselectPin();
+ this._rerenderTimeout && clearTimeout(this._rerenderTimeout);
Object.keys(this._disposers).forEach(key => this._disposers[key]?.());
}
@@ -139,13 +142,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return this.addDocument(doc, sidebarKey); // add to sidebar list
};
+ removeMapDocument = (doc: Doc | Doc[], annotationKey?: string) => {
+ const docs = doc instanceof Doc ? [doc] : doc;
+ this.allAnnotations.filter(anno => docs.includes(DocCast(anno.mapPin))).forEach(anno => (anno.mapPin = undefined));
+ return this.removeDocument(doc, annotationKey, undefined);
+ };
+
/**
* Removing documents from the sidebar
* @param doc
* @param sidebarKey
* @returns
*/
- sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeDocument(doc, sidebarKey);
+ sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeMapDocument(doc, sidebarKey);
/**
* Toggle sidebar onclick the tiny comment button on the top right corner
@@ -208,10 +217,41 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
};
+ startAnchorDrag = (e: PointerEvent, ele: HTMLElement) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const sourceAnchorCreator = action(() => {
+ const note = this.getAnchor(true);
+ if (note && this.selectedPin) {
+ note.latitude = this.selectedPin.latitude;
+ note.longitude = this.selectedPin.longitude;
+ note.map = this.selectedPin.map;
+ }
+ return note as Doc;
+ });
+
+ const targetCreator = (annotationOn: Doc | undefined) => {
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+ FormattedTextBox.SelectOnLoad = target[Id];
+ return target;
+ };
+ const docView = this.props.DocumentView?.();
+ docView &&
+ DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
+ dragComplete: e => {
+ if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
+ e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.Document;
+ e.annoDragData.linkSourceDoc.followLinkZoom = false;
+ }
+ },
+ });
+ };
+
createNoteAnnotation = () => {
const createFunc = undoable(
action(() => {
- const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false), ['latitude', 'longitude', '-linkedTo']);
+ const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', '-linkedTo']);
if (note && this.selectedPin) {
note.latitude = this.selectedPin.latitude;
note.longitude = this.selectedPin.longitude;
@@ -236,7 +276,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return false;
};
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
@action
onMarqueeDown = (e: React.PointerEvent) => {
@@ -257,7 +297,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
@action finishMarquee = (x?: number, y?: number) => {
this._marqueeing = undefined;
- x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false);
+ x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false, this.rootDoc);
};
addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
@@ -326,7 +366,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
NumCast(longitude),
false,
[],
- { map: map }
+ { title: map ?? `lat=${latitude},lng=${longitude}`, map: map }
// ,'pushpinIDamongus'+ this.incrementer++
);
this.addDocument(pushpin, this.annotationKey);
@@ -343,7 +383,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
// Removes filter
Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
- Doc.setDocFilter(this.rootDoc, '-linkedTo', Field.toString(DocCast(this.selectedPin.mapPin)), 'removeAll');
+ Doc.setDocFilter(this.rootDoc, '-linkedTo', `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
const temp = this.selectedPin;
if (!this._unmounting) {
@@ -375,13 +415,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
// Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match');
// Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match');
- Doc.setDocFilter(this.rootDoc, '-linkedTo', Field.toScriptString(this.selectedPin), 'mapPin' as any);
+ Doc.setDocFilter(this.rootDoc, '-linkedTo', `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check');
this.recolorPin(this.selectedPin, 'green');
MapAnchorMenu.Instance.Delete = this.deleteSelectedPin;
MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
- MapAnchorMenu.Instance.LinkNote = this.createNoteAnnotation;
+ MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
+ MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude));
const x = point.x + (this.props.PanelWidth() - this.sidebarWidth()) / 2;
@@ -451,11 +492,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
config_map_type: StrCast(this.dataDoc.map_type),
config_map: StrCast((existingPin ?? this.selectedPin)?.map) || StrCast(this.dataDoc.map),
layout_unrendered: true,
+ mapPin: existingPin ?? this.selectedPin,
+ annotationOn: this.rootDoc,
});
if (anchor) {
- anchor.mapPin = existingPin ?? this.selectedPin;
if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
- /* addAsAnnotation &&*/ this.addDocument(anchor);
+ addAsAnnotation && this.addDocument(anchor);
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.rootDoc);
return anchor;
}
@@ -489,7 +531,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
* Removes pin from annotations
*/
@action
- removePushpin = (pinDoc: Doc) => this.removeDocument(pinDoc, this.annotationKey);
+ removePushpin = (pinDoc: Doc) => this.removeMapDocument(pinDoc, this.annotationKey);
/*
* Removes pushpin from map render
@@ -508,7 +550,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
// Removes filter
Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
- Doc.setDocFilter(this.rootDoc, '-linkedTo', Field.toString(DocCast(this.selectedPin.mapPin)), 'removeAll');
+ Doc.setDocFilter(this.rootDoc, '-linkedTo', `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
this.removePushpin(this.selectedPin);
}
@@ -683,20 +725,20 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch();
static _firstRender = true;
- static _rerenderDelay = 0;
+ static _rerenderDelay = 500;
_rerenderTimeout: any;
render() {
// bcz: no idea what's going on here, but bings maps have some kind of bug
// such that we need to delay rendering a second map on startup until the first map is rendered.
this.rootDoc[DocCss];
- if (MapBox._firstRender) {
- MapBox._firstRender = false;
- MapBox._rerenderDelay = 500;
- } else if (MapBox._rerenderDelay) {
+ if (MapBox._rerenderDelay) {
// prettier-ignore
this._rerenderTimeout = this._rerenderTimeout ??
setTimeout(action(() => {
- MapBox._rerenderDelay = 0;
+ if ((window as any).Microsoft?.Maps?.Internal._WorkDispatcher) {
+ MapBox._rerenderDelay = 0;
+ }
+ this._rerenderTimeout = undefined;
this.rootDoc[DocCss] = this.rootDoc[DocCss] + 1;
}), MapBox._rerenderDelay);
return null;
@@ -753,8 +795,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
? null
: this.allAnnotations
.filter(anno => !anno.layout_unrendered)
- .map(pushpin => (
+ .map((pushpin, i) => (
<DocumentView
+ key={i}
{...this.props}
renderDepth={this.props.renderDepth + 1}
Document={pushpin}
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index a54bdcd5e..407a91dd0 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -98,7 +98,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
public get SidebarKey() {
return this.fieldKey + '_sidebar';
}
- private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
+ private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
@computed get inlineTextAnnotations() {
return this.allMapMarkers.filter(a => a.text_inlineAnnotations);
}
@@ -502,7 +502,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action finishMarquee = (x?: number, y?: number) => {
this._marqueeing = undefined;
this._isAnnotating = false;
- x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false);
+ x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false, this.props.Document);
};
addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 758b49655..cf44649a2 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -24,7 +24,6 @@ import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
import { Colors } from '../global/globalEnums';
-import { LightboxView } from '../LightboxView';
import { CreateImage } from '../nodes/WebBoxRenderer';
import { PDFViewer } from '../pdf/PDFViewer';
import { SidebarAnnos } from '../SidebarAnnos';
@@ -243,14 +242,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
title: StrCast(this.rootDoc.title + '@' + NumCast(this.layoutDoc._layout_scrollTop)?.toFixed(0)),
annotationOn: this.rootDoc,
});
- const annoAnchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations(), true);
- const anchor = annoAnchor ?? docAnchor();
+ const visibleAnchor = this._pdfViewer?._getAnchor?.(this._pdfViewer.savedAnnotations(), true);
+ const anchor = visibleAnchor ?? docAnchor();
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true, pannable: true } }, this.rootDoc);
anchor.text = ele?.textContent ?? '';
anchor.text_html = ele?.innerHTML;
- if (addAsAnnotation || annoAnchor) {
- this.addDocument(anchor);
- }
+ addAsAnnotation && this.addDocument(anchor);
+
return anchor;
};
@@ -439,12 +437,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return PDFBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
};
@undoBatch
- toggleSidebarType = () => (this.rootDoc.sidebar_collectionType = this.rootDoc.sidebar_collectionType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
+ toggleSidebarType = () => (this.rootDoc[this.SidebarKey + '_type_collection'] = this.rootDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
const options = cm.findByDescription('Options...');
const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
- optionItems.push({ description: 'Toggle Sidebar Type', event: this.toggleSidebarType, icon: 'expand-arrows-alt' });
+ !Doc.noviceMode && optionItems.push({ description: 'Toggle Sidebar Type', event: this.toggleSidebarType, icon: 'expand-arrows-alt' });
!Doc.noviceMode && optionItems.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' });
//optionItems.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
!options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'asterisk' });
@@ -555,7 +553,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
return (
<div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: '100%', right: 0, backgroundColor: `white` }}>
- {renderComponent(StrCast(this.layoutDoc.sidebar_collectionType))}
+ {renderComponent(StrCast(this.layoutDoc[this.SidebarKey + '_type_collection']))}
</div>
);
}
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 8fa2861b6..481e43feb 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -1,20 +1,27 @@
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { DateField } from '../../../../fields/DateField';
+import { Doc, DocListCast } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { List } from '../../../../fields/List';
+import { BoolCast, DocCast } from '../../../../fields/Types';
import { VideoField } from '../../../../fields/URLField';
import { Upload } from '../../../../server/SharedMediaTypes';
+import { Docs } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { DragManager } from '../../../util/DragManager';
+import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { Presentation } from '../../../util/TrackMovements';
+import { undoBatch } from '../../../util/UndoManager';
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
import { ViewBoxBaseComponent } from '../../DocComponent';
+import { media_state } from '../AudioBox';
import { FieldView, FieldViewProps } from '../FieldView';
import { VideoBox } from '../VideoBox';
import { RecordingView } from './RecordingView';
-import { DocumentType } from '../../../documents/DocumentTypes';
-import { Presentation } from '../../../util/TrackMovements';
-import { Doc } from '../../../../fields/Doc';
-import { Id } from '../../../../fields/FieldSymbols';
-import { BoolCast, DocCast } from '../../../../fields/Types';
-import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { Docs } from '../../../documents/Documents';
@observer
export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -34,9 +41,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
@observable videoDuration: number | undefined = undefined;
@action
- setVideoDuration = (duration: number) => {
- this.videoDuration = duration;
- };
+ setVideoDuration = (duration: number) => (this.videoDuration = duration);
@action
setResult = (info: Upload.AccessPathInfo, presentation?: Presentation) => {
@@ -54,6 +59,128 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.dataDoc[this.fieldKey + '_presentation'] = JSON.stringify(presCopy);
}
};
+ @undoBatch
+ @action
+ public static WorkspaceStopRecording() {
+ const remDoc = RecordingBox.screengrabber?.rootDoc;
+ if (remDoc) {
+ //if recordingbox is true; when we press the stop button. changed vals temporarily to see if changes happening
+ RecordingBox.screengrabber?.Pause?.();
+ setTimeout(() => {
+ RecordingBox.screengrabber?.Finish?.();
+ remDoc.overlayX = 70; //was 100
+ remDoc.overlayY = 590;
+ RecordingBox.screengrabber = undefined;
+ }, 100);
+ //could break if recording takes too long to turn into videobox. If so, either increase time on setTimeout below or find diff place to do this
+ setTimeout(() => Doc.RemFromMyOverlay(remDoc), 1000);
+ Doc.UserDoc().workspaceRecordingState = media_state.Paused;
+ Doc.AddDocToList(Doc.UserDoc(), 'workspaceRecordings', remDoc);
+ }
+ }
+
+ /**
+ * This method toggles whether or not we are currently using the RecordingBox to record with the topbar button
+ * @param _readOnly_
+ * @returns
+ */
+ @undoBatch
+ @action
+ public static WorkspaceStartRecording(value: string) {
+ const screengrabber =
+ value === 'Record Workspace'
+ ? Docs.Create.ScreenshotDocument({
+ title: `${new DateField()}-${Doc.ActiveDashboard?.title ?? ''}`,
+ _width: 205,
+ _height: 115,
+ })
+ : Docs.Create.WebCamDocument(`${new DateField()}-${Doc.ActiveDashboard?.title ?? ''}`, {
+ title: `${new DateField()}-${Doc.ActiveDashboard?.title ?? ''}`,
+ _width: 205,
+ _height: 115,
+ });
+ screengrabber.overlayX = 70; //was -400
+ screengrabber.overlayY = 590; //was 0
+ Doc.GetProto(screengrabber)[Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
+ Doc.AddToMyOverlay(screengrabber); //just adds doc to overlay
+ DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => {
+ RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
+ RecordingBox.screengrabber.Record?.();
+ });
+ Doc.UserDoc().workspaceRecordingState = media_state.Recording;
+ }
+
+ /**
+ * This method changes the menu depending on whether or not we are in playback mode
+ * @param value RecordingBox rootdoc
+ */
+ @undoBatch
+ @action
+ public static replayWorkspace(value: Doc) {
+ Doc.UserDoc().currentRecording = value;
+ value.overlayX = 70;
+ value.overlayY = window.innerHeight - 180;
+ Doc.AddToMyOverlay(value);
+ DocumentManager.Instance.AddViewRenderedCb(value, docView => {
+ Doc.UserDoc().currentRecording = docView.rootDoc;
+ SelectionManager.SelectSchemaViewDoc(value);
+ RecordingBox.resumeWorkspaceReplaying(value);
+ });
+ }
+
+ /**
+ * Adds the recording box to the canvas
+ * @param value current recordingbox
+ */
+ @undoBatch
+ @action
+ public static addRecToWorkspace(value: RecordingBox) {
+ let ffView = Array.from(DocumentManager.Instance.DocumentViews).find(view => view.ComponentView instanceof CollectionFreeFormView);
+ (ffView?.ComponentView as CollectionFreeFormView).props.addDocument?.(value.rootDoc);
+ Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value.rootDoc);
+ Doc.RemFromMyOverlay(value.rootDoc);
+ Doc.UserDoc().currentRecording = undefined;
+ Doc.UserDoc().workspaceReplayingState = undefined;
+ Doc.UserDoc().workspaceRecordingState = undefined;
+ }
+
+ @action
+ public static resumeWorkspaceReplaying(doc: Doc) {
+ const docView = DocumentManager.Instance.getDocumentView(doc);
+ if (docView?.ComponentView instanceof VideoBox) {
+ docView.ComponentView.Play();
+ }
+ Doc.UserDoc().workspaceReplayingState = media_state.Playing;
+ }
+
+ @action
+ public static pauseWorkspaceReplaying(doc: Doc) {
+ const docView = DocumentManager.Instance.getDocumentView(doc);
+ const videoBox = docView?.ComponentView as VideoBox;
+ if (videoBox) {
+ videoBox.Pause();
+ }
+ Doc.UserDoc().workspaceReplayingState = media_state.Paused;
+ }
+
+ @action
+ public static stopWorkspaceReplaying(value: Doc) {
+ Doc.RemFromMyOverlay(value);
+ Doc.UserDoc().currentRecording = undefined;
+ Doc.UserDoc().workspaceReplayingState = undefined;
+ Doc.UserDoc().workspaceRecordingState = undefined;
+ Doc.RemFromMyOverlay(value);
+ }
+
+ @undoBatch
+ @action
+ public static removeWorkspaceReplaying(value: Doc) {
+ Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value);
+ Doc.RemFromMyOverlay(value);
+ Doc.UserDoc().currentRecording = undefined;
+ Doc.UserDoc().workspaceReplayingState = undefined;
+ Doc.UserDoc().workspaceRecordingState = undefined;
+ }
Record: undefined | (() => void);
Pause: undefined | (() => void);
@@ -81,28 +208,52 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
static screengrabber: RecordingBox | undefined;
}
-ScriptingGlobals.add(function toggleRecording(_readOnly_: boolean) {
- if (_readOnly_) return RecordingBox.screengrabber ? true : false;
- if (RecordingBox.screengrabber) {
- RecordingBox.screengrabber.Pause?.();
- setTimeout(() => {
- RecordingBox.screengrabber?.Finish?.();
- RecordingBox.screengrabber!.rootDoc.overlayX = 100;
- RecordingBox.screengrabber!.rootDoc.overlayY = 100;
- RecordingBox.screengrabber = undefined;
- }, 100);
- } else {
- const screengrabber = Docs.Create.WebCamDocument('', {
- _width: 384,
- _height: 216,
- });
- screengrabber.overlayX = -400;
- screengrabber.overlayY = 0;
- screengrabber[Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
- Doc.AddToMyOverlay(screengrabber);
- DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => {
- RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
- RecordingBox.screengrabber.Record?.();
- });
+
+ScriptingGlobals.add(function stopWorkspaceRecording() {
+ RecordingBox.WorkspaceStopRecording();
+});
+
+ScriptingGlobals.add(function stopWorkspaceReplaying(value: Doc) {
+ RecordingBox.stopWorkspaceReplaying(value);
+});
+ScriptingGlobals.add(function removeWorkspaceReplaying(value: Doc) {
+ RecordingBox.removeWorkspaceReplaying(value);
+});
+
+ScriptingGlobals.add(function getCurrentRecording() {
+ return Doc.UserDoc().currentRecording;
+});
+ScriptingGlobals.add(function getWorkspaceRecordings() {
+ return new List<any>(['Record Workspace', `Record Webcam`, ...DocListCast(Doc.UserDoc().workspaceRecordings)]);
+});
+ScriptingGlobals.add(function isWorkspaceRecording() {
+ return Doc.UserDoc().workspaceRecordingState === media_state.Recording;
+});
+ScriptingGlobals.add(function isWorkspaceReplaying() {
+ return Doc.UserDoc().workspaceReplayingState;
+});
+ScriptingGlobals.add(function replayWorkspace(value: Doc | string, _readOnly_: boolean) {
+ if (_readOnly_) return DocCast(Doc.UserDoc().currentRecording) ?? 'Record Workspace';
+ if (typeof value === 'string') RecordingBox.WorkspaceStartRecording(value);
+ else RecordingBox.replayWorkspace(value);
+});
+ScriptingGlobals.add(function pauseWorkspaceReplaying(value: Doc, _readOnly_: boolean) {
+ RecordingBox.pauseWorkspaceReplaying(value);
+});
+ScriptingGlobals.add(function resumeWorkspaceReplaying(value: Doc, _readOnly_: boolean) {
+ RecordingBox.resumeWorkspaceReplaying(value);
+});
+
+ScriptingGlobals.add(function startRecordingDrag(value: { doc: Doc | string; e: React.PointerEvent }) {
+ if (DocCast(value.doc)) {
+ DragManager.StartDocumentDrag([value.e.target as HTMLElement], new DragManager.DocumentDragData([DocCast(value.doc)], 'embed'), value.e.clientX, value.e.clientY);
+ value.e.preventDefault();
+ return true;
+ }
+});
+ScriptingGlobals.add(function renderDropdown() {
+ if (!Doc.UserDoc().workspaceRecordings || DocListCast(Doc.UserDoc().workspaceRecordings).length === 0) {
+ return true;
}
-}, 'toggle recording');
+ return false;
+});
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx
index 0e386b093..f7ed82643 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx
@@ -164,7 +164,6 @@ export function RecordingView(props: IRecordingViewProps) {
const finish = () => {
// call stop on the video recorder if active
videoRecorder.current?.state !== 'inactive' && videoRecorder.current?.stop();
-
// end the streams (audio/video) to remove recording icon
const stream = videoElementRef.current!.srcObject;
stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop());
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index fa19caae1..ebb8a3374 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -26,6 +26,8 @@ import { FieldView, FieldViewProps } from './FieldView';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import './ScreenshotBox.scss';
import { VideoBox } from './VideoBox';
+import { TrackMovements } from '../../util/TrackMovements';
+import { media_state } from './AudioBox';
declare class MediaRecorder {
constructor(e: any, options?: any); // whatever MediaRecorder has
@@ -180,7 +182,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
ref={r => {
this._videoRef = r;
setTimeout(() => {
- if (this.rootDoc.mediaState === 'pendingRecording' && this._videoRef) {
+ if (this.rootDoc.mediaState === media_state.PendingRecording && this._videoRef) {
this.toggleRecording();
}
}, 100);
@@ -219,6 +221,9 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
// }
return null;
}
+ Record = () => !this._screenCapture && this.toggleRecording();
+ Pause = () => this._screenCapture && this.toggleRecording();
+
toggleRecording = async () => {
if (!this._screenCapture) {
this._audioRec = new MediaRecorder(await navigator.mediaDevices.getUserMedia({ audio: true }));
@@ -233,9 +238,19 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
this._videoRef!.srcObject = await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
this._videoRec = new MediaRecorder(this._videoRef!.srcObject);
const vid_chunks: any = [];
- this._videoRec.onstart = () => (this.dataDoc[this.props.fieldKey + '_recordingStart'] = new DateField(new Date()));
+ this._videoRec.onstart = () => {
+ if (this.dataDoc[this.props.fieldKey + '_trackScreen']) TrackMovements.Instance.start();
+ this.dataDoc[this.props.fieldKey + '_recordingStart'] = new DateField(new Date());
+ };
this._videoRec.ondataavailable = (e: any) => vid_chunks.push(e.data);
this._videoRec.onstop = async (e: any) => {
+ const presentation = TrackMovements.Instance.yieldPresentation();
+ if (presentation?.movements) {
+ const presCopy = { ...presentation };
+ presCopy.movements = presentation.movements.map(movement => ({ ...movement, doc: movement.doc[Id] })) as any;
+ this.dataDoc[this.fieldKey + '_presentation'] = JSON.stringify(presCopy);
+ }
+ TrackMovements.Instance.finish();
const file = new File(vid_chunks, `${this.rootDoc[Id]}.mkv`, { type: vid_chunks[0].type, lastModified: Date.now() });
const [{ result }] = await Networking.UploadFilesToServer({ file });
this.dataDoc[this.fieldKey + '_duration'] = (new Date().getTime() - this.recordingStart!) / 1000;
diff --git a/src/client/views/nodes/SliderBox.scss b/src/client/views/nodes/SliderBox.scss
deleted file mode 100644
index 4206a368d..000000000
--- a/src/client/views/nodes/SliderBox.scss
+++ /dev/null
@@ -1,19 +0,0 @@
-.sliderBox-outerDiv {
- width: calc(100% - 14px); // 14px accounts for handles that are at the max value of the slider that would extend outside the box
- height: 100%;
- border-radius: inherit;
- display: flex;
- flex-direction: column;
- position: relative;
- .slider-tracks {
- top: 7px;
- position: relative;
- }
- .slider-ticks {
- position: relative;
- }
- .slider-handles {
- top: 7px;
- position: relative;
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
deleted file mode 100644
index 430b20eb5..000000000
--- a/src/client/views/nodes/SliderBox.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
-import { NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { ContextMenu } from '../ContextMenu';
-import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxBaseComponent } from '../DocComponent';
-import { ScriptBox } from '../ScriptBox';
-import { StyleProp } from '../StyleProvider';
-import { FieldView, FieldViewProps } from './FieldView';
-import { Handle, Tick, TooltipRail, Track } from './SliderBox-components';
-import './SliderBox.scss';
-
-@observer
-export class SliderBox extends ViewBoxBaseComponent<FieldViewProps>() {
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(SliderBox, fieldKey);
- }
-
- get minThumbKey() {
- return this.fieldKey + '-minThumb';
- }
- get maxThumbKey() {
- return this.fieldKey + '-maxThumb';
- }
- get minKey() {
- return this.fieldKey + '-min';
- }
- get maxKey() {
- return this.fieldKey + '-max';
- }
- specificContextMenu = (e: React.MouseEvent): void => {
- const funcs: ContextMenuProps[] = [];
- funcs.push({ description: 'Edit Thumb Change Script', icon: 'edit', event: (obj: any) => ScriptBox.EditButtonScript('On Thumb Change ...', this.props.Document, 'onThumbChange', obj.x, obj.y) });
- ContextMenu.Instance.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
- };
- onChange = (values: readonly number[]) =>
- runInAction(() => {
- this.dataDoc[this.minThumbKey] = values[0];
- this.dataDoc[this.maxThumbKey] = values[1];
- ScriptCast(this.layoutDoc.onThumbChanged, null)?.script.run({
- self: this.rootDoc,
- scriptContext: this.props.scriptContext,
- range: values,
- this: this.layoutDoc,
- });
- });
-
- render() {
- const domain = [NumCast(this.layoutDoc[this.minKey]), NumCast(this.layoutDoc[this.maxKey])];
- const defaultValues = [NumCast(this.dataDoc[this.minThumbKey]), NumCast(this.dataDoc[this.maxThumbKey])];
- return domain[1] <= domain[0] ? null : (
- <div className="sliderBox-outerDiv" onContextMenu={this.specificContextMenu} onPointerDown={e => e.stopPropagation()} style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}>
- <div
- className="sliderBox-mainButton"
- onContextMenu={this.specificContextMenu}
- style={{
- background: StrCast(this.layoutDoc.backgroundColor),
- color: StrCast(this.layoutDoc.color, 'black'),
- fontSize: StrCast(this.layoutDoc._text_fontSize),
- letterSpacing: StrCast(this.layoutDoc.letterSpacing),
- }}>
- <Slider mode={2} step={Math.min(1, 0.1 * (domain[1] - domain[0]))} domain={domain} rootStyle={{ position: 'relative', width: '100%' }} onChange={this.onChange} values={defaultValues}>
- <Rail>{railProps => <TooltipRail {...railProps} />}</Rail>
- <Handles>
- {({ handles, activeHandleID, getHandleProps }) => (
- <div className="slider-handles">
- {handles.map((handle, i) => {
- const value = i === 0 ? defaultValues[0] : defaultValues[1];
- return (
- <div title={String(value)}>
- <Handle key={handle.id} handle={handle} domain={domain} isActive={handle.id === activeHandleID} getHandleProps={getHandleProps} />
- </div>
- );
- })}
- </div>
- )}
- </Handles>
- <Tracks left={false} right={false}>
- {({ tracks, getTrackProps }) => (
- <div className="slider-tracks">
- {tracks.map(({ id, source, target }) => (
- <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} />
- ))}
- </div>
- )}
- </Tracks>
- <Ticks count={5}>
- {({ ticks }) => (
- <div className="slider-ticks">
- {ticks.map(tick => (
- <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} />
- ))}
- </div>
- )}
- </Ticks>
- </Slider>
- </div>
- </div>
- );
- }
-}
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index 5e1359441..f803715ad 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -84,7 +84,7 @@
width: 0;
height: 0;
position: relative;
- z-index: 100001;
+ z-index: 2000;
}
.videoBox-ui {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 48716b867..d7f7c9b73 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -29,7 +29,7 @@ import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComp
import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
-import { DocumentView, OpenWhere } from './DocumentView';
+import { DocFocusOptions, DocumentView, OpenWhere } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { RecordingBox } from './RecordingBox';
import { PinProps, PresBox } from './trails';
@@ -57,6 +57,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
static _youtubeIframeCounter: number = 0;
static heightPercent = 80; // height of video relative to videoBox when timeline is open
static numThumbnails = 20;
+ private unmounting = false;
private _disposers: { [name: string]: IReactionDisposer } = {};
private _youtubePlayer: YT.Player | undefined = undefined;
private _videoRef: HTMLVideoElement | null = null; // <video> ref
@@ -124,6 +125,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
componentDidMount() {
+ this.unmounting = false;
this.props.setContentView?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
if (this.youtubeVideoId) {
const youtubeaspect = 400 / 315;
@@ -135,7 +137,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
}
}
- this.player && this.setPlayheadTime(0);
+ this.player && this.setPlayheadTime(this.timeline.clipStart || 0);
document.addEventListener('keydown', this.keyEvents, true);
if (this.presentation) {
@@ -144,6 +146,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
componentWillUnmount() {
+ this.unmounting = true;
this.removeCurrentlyPlaying();
this.Pause();
Object.keys(this._disposers).forEach(d => this._disposers[d]?.());
@@ -191,7 +194,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._finished = false;
start = this.timeline.trimStart;
}
-
try {
this._audioPlayer && this.player && (this._audioPlayer.currentTime = this.player?.currentTime);
update && this.player && this.playFrom(start, undefined, true);
@@ -386,7 +388,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
const timecode = Cast(this.layoutDoc._layout_currentTimecode, 'number', null);
const marquee = AnchorMenu.Instance.GetAnchor?.(undefined, addAsAnnotation);
if (!addAsAnnotation && marquee) marquee.backgroundColor = 'transparent';
- const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || this.rootDoc;
+ const anchor = addAsAnnotation
+ ? CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || this.rootDoc
+ : Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.rootDoc });
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true } }, this.rootDoc);
return anchor;
};
@@ -408,7 +412,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// updates video time
@action
updateTimecode = () => {
- this.player && (this.layoutDoc._layout_currentTimecode = this.player.currentTime);
+ !this.unmounting && this.player && (this.layoutDoc._layout_currentTimecode = this.player.currentTime);
try {
this._youtubePlayer && (this.layoutDoc._layout_currentTimecode = this._youtubePlayer.getCurrentTime?.());
} catch (e) {
@@ -847,11 +851,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
zoom = (zoom: number) => this.timeline?.setZoom(zoom);
// plays link
- playLink = (doc: Doc) => {
- const startTime = Math.max(0, this._stackedTimeline?.anchorStart(doc) || 0);
+ playLink = (doc: Doc, options: DocFocusOptions) => {
+ const startTime = Math.max(0, NumCast(doc.config_clipStart, this._stackedTimeline?.anchorStart(doc) || 0));
const endTime = this.timeline?.anchorEnd(doc);
if (startTime !== undefined) {
- if (!this.layoutDoc.dontAutoPlayFollowedLinks) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime);
+ if (options.playMedia) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime);
else this.Seek(startTime);
}
};
@@ -1039,7 +1043,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this.props.bringToFront(cropping);
return cropping;
};
-
savedAnnotations = () => this._savedAnnotations;
render() {
const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
@@ -1077,8 +1080,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
fieldKey={this.annotationKey}
isAnnotationOverlay={true}
annotationLayerHostsContent={true}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
+ PanelWidth={this.props.PanelWidth}
+ PanelHeight={this.props.PanelHeight}
isAnyChildContentActive={returnFalse}
ScreenToLocalTransform={this.screenToLocalTransform}
childFilters={this.timelineDocFilter}
@@ -1119,7 +1122,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@computed get UIButtons() {
const bounds = this.props.docViewPath().lastElement().getBounds();
const width = (bounds?.right || 0) - (bounds?.left || 0);
- const curTime = NumCast(this.layoutDoc._layout_currentTimecode) - (this.timeline?.clipStart || 0);
+ const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
return (
<>
<div className="videobox-button" title={this._playing ? 'play' : 'pause'} onPointerDown={this.onPlayDown}>
@@ -1128,7 +1131,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
{this.timeline && width > 150 && (
<div className="timecode-controls">
- <div className="timecode-current">{formatTime(curTime)}</div>
+ <div className="timecode-current">{formatTime(curTime - (this.timeline?.clipStart || 0))}</div>
{this._fullScreen || (this.heightPercent === 100 && width > 200) ? (
<div className="timeline-slider">
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 75847c100..511c91da0 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -176,7 +176,6 @@
width: 100%;
height: 100%;
transform-origin: top left;
- overflow: auto;
.webBox-iframe {
width: 100%;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index febf8341e..af20ff061 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -13,7 +13,7 @@ import { listSpec } from '../../../fields/Schema';
import { Cast, ImageCast, NumCast, StrCast, WebCast } from '../../../fields/Types';
import { ImageField, WebField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, getWordAtPoint, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from '../../util/DragManager';
@@ -50,7 +50,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
public static openSidebarWidth = 250;
public static sidebarResizerWidth = 5;
static webStyleSheet = addStyleSheet();
- private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
+ private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
private _setBrushViewer: undefined | ((view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void);
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _outerRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -62,10 +62,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
private _searchRef = React.createRef<HTMLInputElement>();
private _searchString = '';
private _scrollTimer: any;
+ private _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
- private get _getAnchor() {
- return AnchorMenu.Instance?.GetAnchor;
- }
@observable private _webUrl = ''; // url of the src parameter of the embedded iframe but not necessarily the rendered page - eg, when following a link, the rendered page changes but we don't want the src parameter to also change as that would cause an unnecessary re-render.
@observable private _hackHide = false; // apparently changing the value of the 'sandbox' prop doesn't necessarily apply it to the active iframe. so thisforces the ifrmae to be rebuilt when allowScripts is toggled
@observable private _searching: boolean = false;
@@ -186,18 +184,25 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document. this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
runInAction(() => {
- this._annotationKeySuffix = () => this._urlHash + '_annotations';
- const reqdFuncs: { [key: string]: string } = {};
+ this._annotationKeySuffix = () => (this._urlHash ? this._urlHash + '_' : '') + 'annotations';
// bcz: need to make sure that doc.data_annotations points to the currently active web page's annotations (this could/should be when the doc is created)
- reqdFuncs[this.fieldKey + '_annotations'] = `copyField(this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"_annotations"])`;
- reqdFuncs[this.fieldKey + '_annotations-setter'] = `this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"_annotations"] = value`;
- reqdFuncs[this.fieldKey + '_sidebar'] = `copyField(this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"_sidebar"])`;
- DocUtils.AssignScripts(this.dataDoc, {}, reqdFuncs);
+ if (this._url) {
+ const reqdFuncs: { [key: string]: string } = {};
+ reqdFuncs[this.fieldKey + '_annotations'] = `copyField(this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"annotations"])`;
+ reqdFuncs[this.fieldKey + '_annotations-setter'] = `this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"annotations"] = value`;
+ reqdFuncs[this.fieldKey + '_sidebar'] = `copyField(this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"sidebar"])`;
+ DocUtils.AssignScripts(this.dataDoc, {}, reqdFuncs);
+ }
});
this._disposers.urlchange = reaction(
() => WebCast(this.rootDoc.data),
+ url => this.submitURL(false, false)
+ );
+ this._disposers.titling = reaction(
+ () => StrCast(this.rootDoc.title),
url => {
- this.submitURL(url.url.href, false, false);
+ url.startsWith('www') && this.setData('http://' + url);
+ url.startsWith('http') && this.setData(url);
}
);
@@ -259,14 +264,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const clientRects = selRange.getClientRects();
for (let i = 0; i < clientRects.length; i++) {
const rect = clientRects.item(i);
+ const mainrect = this._url ? { translateX: 0, translateY: 0, scale: 1 } : Utils.GetScreenTransform(this._mainCont.current);
if (rect && rect.width !== this._mainCont.current.clientWidth) {
const annoBox = document.createElement('div');
annoBox.className = 'marqueeAnnotator-annotationBox';
+ const scale = this._url ? 1 : this.props.ScreenToLocalTransform().Scale;
// transforms the positions from screen onto the pdf div
- annoBox.style.top = (rect.top + this._mainCont.current.scrollTop).toString();
- annoBox.style.left = rect.left.toString();
- annoBox.style.width = rect.width.toString();
- annoBox.style.height = rect.height.toString();
+ annoBox.style.top = ((rect.top - mainrect.translateY) * scale + (this._url ? this._mainCont.current.scrollTop : NumCast(this.layoutDoc.layout_scrollTop))).toString();
+ annoBox.style.left = ((rect.left - mainrect.translateX) * scale).toString();
+ annoBox.style.width = (rect.width * scale).toString();
+ annoBox.style.height = (rect.height * scale).toString();
this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, 1);
}
}
@@ -325,8 +332,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
ele.append(contents);
}
} catch (e) {}
+ const visibleAnchor = this._getAnchor(this._savedAnnotations, false);
const anchor =
- this._getAnchor(this._savedAnnotations, false) ??
+ visibleAnchor ??
Docs.Create.ConfigDocument({
title: StrCast(this.rootDoc.title + ' ' + this.layoutDoc._layout_scrollTop),
y: NumCast(this.layoutDoc._layout_scrollTop),
@@ -334,9 +342,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
});
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: pinProps?.pinData ? true : false, pannable: true } }, this.rootDoc);
anchor.text = ele?.textContent ?? '';
- anchor.text_html = ele?.innerHTML;
- //addAsAnnotation &&
- this.addDocumentWrapper(anchor);
+ anchor.text_html = ele?.innerHTML ?? this._selectionText;
+ addAsAnnotation && this.addDocumentWrapper(anchor);
return anchor;
};
@@ -357,21 +364,57 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined);
AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._layout_scrollTop) * scale);
// Changing which document to add the annotation to (the currently selected WebBox)
- GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}_${this._urlHash}_sidebar`);
+ GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
}
}
};
@action
+ webClipDown = (e: React.PointerEvent) => {
+ const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
+ const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale;
+ const word = getWordAtPoint(e.target, e.clientX, e.clientY);
+ this._setPreviewCursor?.(e.clientX, e.clientY, false, true, this.rootDoc);
+ MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+ e.button !== 2 && (this._marqueeing = [e.clientX, e.clientY]);
+ if (word || (e.target as any)?.className?.includes('rangeslider') || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) {
+ e.stopPropagation();
+ setTimeout(
+ action(() => (this._marqueeing = undefined)),
+ 100
+ ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
+ } else {
+ this._isAnnotating = true;
+ this.props.select(false);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ document.addEventListener('pointerup', this.webClipUp);
+ };
+ webClipUp = (e: PointerEvent) => {
+ document.removeEventListener('pointerup', this.webClipUp);
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
+ const sel = window.getSelection();
+ if (sel && !sel.isCollapsed) {
+ const selRange = sel.getRangeAt(0);
+ this._selectionText = sel.toString();
+ AnchorMenu.Instance.setSelectedText(sel.toString());
+ this._textAnnotationCreator = () => this.createTextAnnotation(sel, selRange);
+ AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
+ // Changing which document to add the annotation to (the currently selected WebBox)
+ GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
+ GPTPopup.Instance.addDoc = this.sidebarAddDocument;
+ }
+ };
+ @action
iframeDown = (e: PointerEvent) => {
- const sel = this._iframe?.contentWindow?.getSelection?.();
const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale;
const word = getWordAtPoint(e.target, e.clientX, e.clientY);
- this._setPreviewCursor?.(e.clientX, e.clientY, false, true);
+ this._setPreviewCursor?.(e.clientX, e.clientY, false, true, this.rootDoc);
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
e.button !== 2 && (this._marqueeing = [e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._layout_scrollTop) * scale]);
- if (word || ((e.target as any) || '').className.includes('rangeslider') || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) {
+ if (word || (e.target as any)?.className?.includes('rangeslider') || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) {
setTimeout(
action(() => (this._marqueeing = undefined)),
100
@@ -392,9 +435,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
ContextMenu.Instance.setIgnoreEvents(true);
}
};
- isFirefox = () => {
- return 'InstallTrigger' in window; // navigator.userAgent.indexOf("Chrome") !== -1;
- };
+ isFirefox = () => 'InstallTrigger' in window; // navigator.userAgent.indexOf("Chrome") !== -1;
iframeClick = () => this._iframeClick;
iframeScaling = () => 1 / this.props.ScreenToLocalTransform().Scale;
@@ -417,6 +458,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
_iframetimeout: any = undefined;
+ @observable _warning = 0;
@action
iframeLoaded = (e: any) => {
const iframe = this._iframe;
@@ -430,6 +472,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
try {
href = iframe?.contentWindow?.location.href;
} catch (e) {
+ runInAction(() => this._warning++);
href = undefined;
}
let requrlraw = decodeURIComponent(href?.replace(Utils.prepend('') + '/corsProxy/', '') ?? this._url.toString());
@@ -462,12 +505,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
// { passive: false }
// );
const initHeights = () => {
- this._scrollHeight = Math.max(this._scrollHeight, (iframeContent.body.children[0] as any)?.scrollHeight || 0);
+ this._scrollHeight = Math.max(this._scrollHeight, iframeContent.body.scrollHeight || 0);
if (this._scrollHeight) {
this.rootDoc.nativeHeight = Math.min(NumCast(this.rootDoc.nativeHeight), this._scrollHeight);
this.layoutDoc.height = Math.min(this.layoutDoc[Height](), (this.layoutDoc[Width]() * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth));
}
};
+ const swidth = Math.max(NumCast(this.layoutDoc.nativeWidth), iframeContent.body.scrollWidth || 0);
+ if (swidth) {
+ const aspectResize = swidth / NumCast(this.rootDoc.nativeWidth);
+ this.rootDoc.nativeWidth = swidth;
+ this.rootDoc.nativeHeight = NumCast(this.rootDoc.nativeHeight) * aspectResize;
+ this.layoutDoc.height = this.layoutDoc[Height]() * aspectResize;
+ }
initHeights();
this._iframetimeout && clearTimeout(this._iframetimeout);
this._iframetimeout = setTimeout(
@@ -545,7 +595,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
goTo = (scrollTop: number, duration: number, easeFunc: 'linear' | 'ease' | undefined) => {
if (this._outerRef.current) {
- const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight());
if (duration) {
smoothScroll(duration, [this._outerRef.current], scrollTop, easeFunc);
this.setDashScrollTop(scrollTop, duration);
@@ -610,9 +659,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
);
};
@action
- submitURL = (newUrl?: string, preview?: boolean, dontUpdateIframe?: boolean) => {
- if (!newUrl) return;
- if (!newUrl.startsWith('http')) newUrl = 'http://' + newUrl;
+ submitURL = (preview?: boolean, dontUpdateIframe?: boolean) => {
try {
if (!preview) {
if (this._webPageHasBeenRendered) {
@@ -668,9 +715,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
!Doc.noviceMode &&
funcs.push({ description: (this.layoutDoc[this.fieldKey + '_useCors'] ? "Don't Use" : 'Use') + ' Cors', event: () => (this.layoutDoc[this.fieldKey + '_useCors'] = !this.layoutDoc[this.fieldKey + '_useCors']), icon: 'snowflake' });
funcs.push({
- description: (this.layoutDoc.allowScripts ? 'Prevent' : 'Allow') + ' Scripts',
+ description: (this.dataDoc[this.fieldKey + '_allowScripts'] ? 'Prevent' : 'Allow') + ' Scripts',
event: () => {
- this.layoutDoc.allowScripts = !this.layoutDoc.allowScripts;
+ this.dataDoc[this.fieldKey + '_allowScripts'] = !this.dataDoc[this.fieldKey + '_allowScripts'];
if (this._iframe) {
runInAction(() => (this._hackHide = true));
setTimeout(action(() => (this._hackHide = false)));
@@ -712,14 +759,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
};
@action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => {
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this._marqueeing = undefined;
this._isAnnotating = false;
this._iframeClick = undefined;
- const sel = this._iframe?.contentDocument?.getSelection();
+ const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
if (sel?.empty) sel.empty(); // Chrome
else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
if (x !== undefined && y !== undefined) {
- this._setPreviewCursor?.(x, y, false, false);
+ this._setPreviewCursor?.(x, y, false, false, this.rootDoc);
ContextMenu.Instance.closeMenu();
ContextMenu.Instance.setIgnoreEvents(false);
if (e?.button === 2 || e?.altKey) {
@@ -729,6 +777,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
};
+ @observable lighttext = false;
+
@computed get urlContent() {
setTimeout(
action(() => {
@@ -740,20 +790,37 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
);
const field = this.rootDoc[this.props.fieldKey];
if (field instanceof HtmlField) {
- return <span className="webBox-htmlSpan" contentEditable onPointerDown={e => e.stopPropagation()} dangerouslySetInnerHTML={{ __html: field.html }} />;
+ return (
+ <span
+ className="webBox-htmlSpan"
+ ref={action((r: any) => {
+ if (r) {
+ this._scrollHeight = Number(getComputedStyle(r).height.replace('px', ''));
+ this.lighttext = Array.from(r.children).some((c: any) => c instanceof HTMLElement && lightOrDark(getComputedStyle(c).color) !== Colors.WHITE);
+ }
+ })}
+ contentEditable
+ onPointerDown={this.webClipDown}
+ dangerouslySetInnerHTML={{ __html: field.html }}
+ />
+ );
}
if (field instanceof WebField) {
const url = this.layoutDoc[this.fieldKey + '_useCors'] ? Utils.CorsProxy(this._webUrl) : this._webUrl;
+ const scripts = this.dataDoc[this.fieldKey + '_allowScripts'] || this._webUrl.includes('wikipedia.org') || this._webUrl.includes('google.com') || this._webUrl.startsWith('https://bing');
+ //if (!scripts) console.log('No scripts for: ' + url);
return (
<iframe
+ key={this._warning}
className="webBox-iframe"
ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))}
+ style={{ pointerEvents: this._isAnyChildContentActive || DocumentView.Interacting ? 'none' : undefined }}
src={url}
onLoad={this.iframeLoaded}
scrolling="no" // ugh.. on windows, I get an inner scroll bar for the iframe's body even though the scrollHeight should be set to the full height of the document.
// the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page
// sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />;
- sandbox={`${this.layoutDoc.allowScripts ? 'allow-scripts' : ''} allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin`}
+ sandbox={`${scripts ? 'allow-scripts' : ''} allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin`}
/>
);
}
@@ -761,7 +828,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
- (doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.config_data = new WebField(this._url)));
+ this._url && (doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.config_data = new WebField(this._url)));
return this.addDocument(doc, annotationKey);
};
@@ -886,6 +953,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
style={{
transform: `scale(${this.zoomScaling()}) translate(${-NumCast(this.layoutDoc.freeform_panX)}px, ${-NumCast(this.layoutDoc.freeform_panY)}px)`,
height: Doc.NativeHeight(this.Document) || undefined,
+ mixBlendMode: this._url || !this.lighttext ? 'multiply' : 'hard-light',
}}
ref={this._annotationLayer}>
{this.inlineTextAnnotations
@@ -993,7 +1061,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth;
panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 48f4c2afd..c5167461b 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -8,7 +8,6 @@ import { NumCast, StrCast } from '../../../../fields/Types';
import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
-import { ColorScheme } from '../../../util/SettingsManager';
import { Transform } from '../../../util/Transform';
import { DocFocusOptions, DocumentView } from '../DocumentView';
import { FormattedTextBox } from './FormattedTextBox';
@@ -22,7 +21,7 @@ export class DashDocView {
this.dom = document.createElement('span');
this.dom.style.position = 'relative';
this.dom.style.textIndent = '0';
- this.dom.style.border = '1px solid ' + StrCast(tbox.layoutDoc.color, Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'dimgray' : 'lightGray');
+ this.dom.style.border = '1px solid ' + StrCast(tbox.layoutDoc.color, 'lightGray');
this.dom.style.width = node.attrs.width;
this.dom.style.height = node.attrs.height;
this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block';
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bd969b527..90ebf5206 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -38,7 +38,7 @@ import { LinkManager } from '../../../util/LinkManager';
import { RTFMarkup } from '../../../util/RTFMarkup';
import { SelectionManager } from '../../../util/SelectionManager';
import { SnappingManager } from '../../../util/SnappingManager';
-import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionStackingView } from '../../collections/CollectionStackingView';
import { CollectionTreeView } from '../../collections/CollectionTreeView';
@@ -71,13 +71,14 @@ import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
import applyDevTools = require('prosemirror-dev-tools');
import React = require('react');
-const translateGoogleApi = require('translate-google-api');
+import { media_state } from '../AudioBox';
+import { setCORS } from 'google-translate-api-browser';
+// setting up cors-anywhere server address
+const translate = setCORS('http://cors-anywhere.herokuapp.com/');
export const GoogleRef = 'googleDocId';
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
-export interface FormattedTextBoxProps {
- allowScroll?: boolean;
-}
+export interface FormattedTextBoxProps {}
@observer
export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() {
public static LayoutString(fieldStr: string) {
@@ -93,6 +94,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
static _userStyleSheet: any = addStyleSheet();
static _hadSelection: boolean = false;
private _sidebarRef = React.createRef<SidebarAnnos>();
+ private _sidebarTagRef = React.createRef<React.Component>();
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
private _editorView: Opt<EditorView>;
@@ -153,11 +155,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@computed get layout_autoHeightMargins() {
return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins);
}
- @computed get _recording() {
- return this.dataDoc?.mediaState === 'recording';
+ @computed get _recordingDictation() {
+ return this.dataDoc?.mediaState === media_state.Recording;
}
- set _recording(value) {
- !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? 'recording' : undefined);
+ set _recordingDictation(value) {
+ !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? media_state.Recording : undefined);
}
@computed get config() {
this._keymap = buildKeymap(schema, this.props);
@@ -265,19 +267,36 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
AnchorMenu.Instance.OnAudio = (e: PointerEvent) => {
!this.layoutDoc.layout_showSidebar && this.toggleSidebar();
const anchor = this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true, true);
+
setTimeout(() => {
const target = this._sidebarRef.current?.anchorMenuClick(anchor);
if (target) {
anchor.followLinkAudio = true;
- DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target));
+ let stopFunc: any;
+ Doc.GetProto(target).mediaState = media_state.Recording;
+ Doc.GetProto(target).audioAnnoState = 'recording';
+ DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
+ let reactionDisposer = reaction(
+ () => target.mediaState,
+ action(dictation => {
+ if (!dictation) {
+ Doc.GetProto(target).audioAnnoState = 'stopped';
+ stopFunc();
+ reactionDisposer();
+ }
+ })
+ );
target.title = ComputedField.MakeFunction(`self["text_audioAnnotations_text"].lastElement()`);
}
});
};
- AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => {
- this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
- return undefined;
- });
+ AnchorMenu.Instance.Highlight = undoable(
+ action((color: string, isLinkButton: boolean) => {
+ this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
+ return undefined;
+ }),
+ 'highlght text'
+ );
AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
AnchorMenu.Instance.StartCropDrag = unimplementedFunction;
/**
@@ -372,7 +391,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
let link;
LinkManager.Links(this.dataDoc).forEach((l, i) => {
const anchor = (l.link_anchor_1 as Doc).annotationOn ? (l.link_anchor_1 as Doc) : (l.link_anchor_2 as Doc).annotationOn ? (l.link_anchor_2 as Doc) : undefined;
- if (anchor && (anchor.annotationOn as Doc).mediaState === 'recording') {
+ if (anchor && (anchor.annotationOn as Doc).mediaState === media_state.Recording) {
linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
linkAnchor = anchor;
link = l;
@@ -439,7 +458,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title));
if (!(cfield instanceof ComputedField)) {
- this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '');
+ this.dataDoc.title = (prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '')).trim();
if (str.startsWith('@') && str.length > 1) {
Doc.AddDocToList(Doc.MyPublishedDocs, undefined, this.rootDoc);
}
@@ -541,7 +560,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.annoDragData) {
de.complete.annoDragData.dropDocCreator = () => this.getAnchor(true);
- e.stopPropagation();
return true;
}
const dragData = de.complete.docDragData;
@@ -648,7 +666,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });
}
if (highlights.includes('My Text')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { background: 'moccasin' });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' });
}
if (highlights.includes('Todo Items')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-todo', { outline: 'black solid 1px' });
@@ -688,7 +706,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
toggleSidebar = (preview: boolean = false) => {
const prevWidth = 1 - this.sidebarWidth() / Number(getComputedStyle(this._ref.current!).width.replace('px', ''));
if (preview) this._showSidebar = true;
- else this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%';
+ else {
+ this.layoutDoc[this.SidebarKey + '_freeform_scale_max'] = 1;
+ this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%';
+ }
this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth);
};
@@ -844,7 +865,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
event: () => (this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI),
icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
});
- uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
+ !Doc.noviceMode && uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
!Doc.noviceMode &&
uicontrols.push({
description: 'Broadcast Message',
@@ -945,14 +966,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
breakupDictation = () => {
- if (this._editorView && this._recording) {
+ if (this._editorView && this._recordingDictation) {
this.stopDictation(true);
this._break = true;
const state = this._editorView.state;
const to = state.selection.to;
const updated = TextSelection.create(state.doc, to, to);
this._editorView.dispatch(state.tr.setSelection(updated).insertText('\n', to));
- if (this._recording) {
+ if (this._recordingDictation) {
this.recordDictation();
}
}
@@ -1242,13 +1263,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (!this.props.dontRegisterView) {
this._disposers.record = reaction(
- () => this._recording,
+ () => this._recordingDictation,
() => {
this.stopDictation(true);
- this._recording && this.recordDictation();
- }
+ this._recordingDictation && this.recordDictation();
+ },
+ { fireImmediately: true }
);
- if (this._recording) setTimeout(this.recordDictation);
+ if (this._recordingDictation) setTimeout(this.recordDictation);
}
var quickScroll: string | undefined = '';
this._disposers.scroll = reaction(
@@ -1546,8 +1568,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@action
componentWillUnmount() {
- if (this._recording) {
- this._recording = !this._recording;
+ if (this._recordingDictation) {
+ this._recordingDictation = !this._recordingDictation;
}
Object.values(this._disposers).forEach(disposer => disposer?.());
this.endUndoTypingBatch();
@@ -1561,7 +1583,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
onPointerDown = (e: React.PointerEvent): void => {
if ((e.nativeEvent as any).handledByInnerReactInstance) {
- return e.stopPropagation();
+ return; //e.stopPropagation();
} else (e.nativeEvent as any).handledByInnerReactInstance = true;
if (this.Document.forceActive) e.stopPropagation();
@@ -1585,7 +1607,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
});
}
- if (this._recording && !e.ctrlKey && e.button === 0) {
+ if (this._recordingDictation && !e.ctrlKey && e.button === 0) {
this.breakupDictation();
}
this._downX = e.clientX;
@@ -1593,10 +1615,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
FormattedTextBoxComment.textBox = this;
if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
- // stop propagation if not in sidebar
- // bcz: Change. drag selecting requires that preventDefault is NOT called. This used to happen in DocumentView,
- // but that's changed, so this shouldn't be needed.
- //e.stopPropagation(); // if the text box is selected, then it consumes all down events
+ // stop propagation if not in sidebar, otherwise nested boxes will lose focus to outer boxes.
+ e.stopPropagation(); // if the text box's content is active, then it consumes all down events
document.addEventListener('pointerup', this.onSelectEnd);
}
}
@@ -1614,8 +1634,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
else if (this.props.isContentActive(true)) {
const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
- // !this.props.isSelected(true) &&
- editor.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(pcords?.pos || 0))));
+ let xpos = pcords?.pos || 0;
+ while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
+ xpos = xpos - 1;
+ }
+ editor.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(xpos))));
let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
while (target && !target.dataset?.targethrefs) target = target.parentElement;
FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
@@ -1758,13 +1781,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const state = this._editorView!.state;
const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
- if (this.layoutDoc.sidebar_collectionType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) {
+ if (this.layoutDoc[this.SidebarKey + '_type_collection'] === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) {
try {
- translateGoogleApi(curText, { from: 'en', to: 'es' }).then((result1: any) => {
+ translate(curText, { from: 'en', to: 'es' }).then((result1: any) => {
setTimeout(
() =>
- translateGoogleApi(result1[0], { from: 'es', to: 'en' }).then((result: any) => {
- this.dataDoc[this.fieldKey + '_translation'] = result1 + '\r\n\r\n' + result[0];
+ translate(result1.text, { from: 'es', to: 'en' }).then((result: any) => {
+ const tb = this._sidebarTagRef.current as FormattedTextBox;
+ tb._editorView?.dispatch(tb._editorView!.state.tr.insertText(result1.text + '\r\n\r\n' + result.text));
}),
1000
);
@@ -1802,7 +1826,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (state.selection.empty || !this._rules!.EnteringStyle) {
this._rules!.EnteringStyle = false;
}
- e.stopPropagation();
+ let stopPropagation = true;
for (var i = state.selection.from; i <= state.selection.to; i++) {
const node = state.doc.resolve(i);
if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
@@ -1821,6 +1845,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
case 'Tab':
e.preventDefault();
break;
+ case 'c':
+ this._editorView?.state.selection.empty && (stopPropagation = false);
+ break;
default:
if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
case ' ':
@@ -1830,6 +1857,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
break;
}
+ if (stopPropagation) e.stopPropagation();
this.startUndoTypingBatch();
};
ondrop = (e: React.DragEvent) => {
@@ -1887,7 +1915,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
.scale(1 / NumCast(this.layoutDoc._freeform_scale, 1) / (this.props.NativeDimScaling?.() || 1));
@computed get audioHandle() {
- return !this._recording ? null : (
+ return !this._recordingDictation ? null : (
<div
className="formattedTextBox-dictation"
onPointerDown={e =>
@@ -1896,7 +1924,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
e,
returnFalse,
emptyFunction,
- action(e => (this._recording = !this._recording))
+ action(e => (this._recordingDictation = !this._recordingDictation))
)
}>
<FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon={'microphone'} size="sm" />
@@ -1948,6 +1976,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
<div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
<ComponentTag
{...this.props}
+ ref={this._sidebarTagRef as any}
setContentView={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
@@ -1970,14 +1999,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
fitContentsToBox={this.fitContentsToBox}
noSidebar={true}
treeViewHideTitle={true}
- fieldKey={this.layoutDoc.sidebar_collectionType === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
+ fieldKey={this.layoutDoc[this.SidebarKey + '_type_collection'] === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
/>
</div>
);
};
return (
<div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
- {renderComponent(StrCast(this.layoutDoc.sidebar_collectionType))}
+ {renderComponent(StrCast(this.layoutDoc[this.SidebarKey + '_type_collection']))}
</div>
);
}
@@ -2025,9 +2054,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
@observable _isHovering = false;
onPassiveWheel = (e: WheelEvent) => {
+ if (e.clientX > this.ProseRef!.getBoundingClientRect().right) {
+ if (this.rootDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform) {
+ // if the scrolled freeform is a child of the sidebar component, we need to let the event go through
+ // so react can let the freeform view handle it. We prevent default to stop any containing views from scrolling
+ e.preventDefault();
+ }
+ return;
+ }
+
// if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
- if (this.props.isContentActive() && !this.props.allowScroll) {
- if (!NumCast(this.layoutDoc._layout_scrollTop) && e.deltaY <= 0) e.preventDefault();
+ if (this.props.isContentActive()) {
+ // prevent default if selected || child is active but this doc isn't scrollable
+ if (
+ (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil(this.props.PanelHeight()) && //
+ (this.props.isSelected() || this.isAnyChildContentActive())
+ ) {
+ e.preventDefault();
+ }
e.stopPropagation();
}
};
@@ -2084,7 +2128,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
ref={this._ref}
style={{
cursor: this.props.isContentActive() ? 'text' : undefined,
- overflow: this.layout_autoHeight && this.props.CollectionFreeFormDocumentView?.() ? 'hidden' : undefined, //x this breaks viewing an layout_autoHeight doc in its own tab, or in the lightbox
height: this.props.height || (this.layout_autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
pointerEvents: Doc.ActiveTool === InkTool.None && !this.props.onBrowseClick?.() ? undefined : 'none',
}}
@@ -2098,10 +2141,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
onPointerDown={this.onPointerDown}
onDoubleClick={this.onDoubleClick}>
<div
- className={`formattedTextBox-outer`}
+ className="formattedTextBox-outer"
ref={this._scrollRef}
style={{
- width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
+ width: this.props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
}}
onScroll={this.onScroll}
@@ -2119,9 +2162,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
/>
</div>
{this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
- {this.noSidebar || this.Document._layout_noSidebar || this.props.dontSelectOnLoad || this.Document._createDocOnCR ? null : this.sidebarHandle}
+ {this.noSidebar || this.Document._layout_noSidebar || this.props.dontSelectOnLoad || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
{this.audioHandle}
- {this.layoutDoc._layout_enableAltContentUI ? this.overlayAlternateIcon : null}
+ {this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null}
</div>
</div>
);
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 9c46459b0..e3ac4fb9d 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -21,6 +21,7 @@ import { updateBullets } from './ProsemirrorExampleTransfer';
import './RichTextMenu.scss';
import { schema } from './schema_rts';
import { EquationBox } from '../EquationBox';
+import { numberRange } from '../../../../Utils';
const { toggleMark } = require('prosemirror-commands');
@observer
@@ -149,19 +150,31 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => {
if (mark) {
- const node = (state.selection as NodeSelection).node;
+ const liFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.list_item);
+ const liTo = numberRange(state.selection.$to.depth + 1).find(i => state.selection.$to.node(i)?.type === state.schema.nodes.list_item);
+ const olFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.ordered_list);
+ const nodeOl = (liFirst && liTo && state.selection.$from.node(liFirst) !== state.selection.$to.node(liTo) && olFirst) || (!liFirst && !liTo && olFirst);
+ const newPos = nodeOl ? numberRange(state.selection.from).findIndex(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) : state.selection.from;
+ const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined);
if (node?.type === schema.nodes.ordered_list) {
let attrs = node.attrs;
if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, fontFamily: mark.attrs.family };
if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, fontSize: mark.attrs.fontSize };
if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, fontColor: mark.attrs.color };
- const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
- dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
- } else if (dontToggle) {
- const tr = state.tr.addMark(state.selection.from, state.selection.to, mark);
- dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
- } else {
- toggleMark(mark.type, mark.attrs)(state, dispatch);
+ const tr = updateBullets(state.tr.setNodeMarkup(newPos, node.type, attrs), state.schema);
+ dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to))));
+ }
+ {
+ const state = this.view?.state;
+ const tr = this.view?.state.tr;
+ if (tr && state) {
+ if (dontToggle) {
+ tr.addMark(state.selection.from, state.selection.to, mark);
+ dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
+ } else {
+ toggleMark(mark.type, mark.attrs)(state, dispatch);
+ }
+ }
}
}
};
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 8bafc2cef..3e2afd2ce 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -280,11 +280,8 @@ export class RichTextRules {
this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
}
const target = DocServer.FindDocByTitle(docTitle);
- if (target) {
- const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target[Id], hideKey: false });
- return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
- }
- return state.tr;
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false });
+ return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
}),
// create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 7e17008bb..6f07588b3 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -348,7 +348,7 @@ export const marks: { [index: string]: MarkSpec } = {
excludes: 'user_mark',
group: 'inline',
toDOM(node: any) {
- const uid = node.attrs.userid.replace('.', '').replace('@', '');
+ const uid = node.attrs.userid.replace(/\./g, '').replace(/@/g, '');
const min = Math.round(node.attrs.modified / 60);
const hr = Math.round(min / 60);
const day = Math.round(hr / 60 / 24);
diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index bf56b4d9e..0b51813a5 100644
--- a/src/client/views/nodes/trails/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -121,7 +121,7 @@
.dropdown.active {
transform: rotate(180deg);
color: $light-blue;
- opacity: 0.8;
+ opacity: 0.7;
}
.presBox-radioButtons {
@@ -187,9 +187,6 @@
font-size: 11;
font-weight: 200;
height: 20;
- background-color: $white;
- color: $black;
- border: solid 1px $black;
display: flex;
margin-left: 5px;
margin-top: 5px;
@@ -210,13 +207,11 @@
.ribbon-propertyUpDownItem {
cursor: pointer;
- color: white;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
- background: $black;
}
.ribbon-propertyUpDownItem:hover {
@@ -609,7 +604,6 @@
font-weight: 200;
height: 20;
background-color: $white;
- border: solid 1px rgba(0, 0, 0, 0.5);
display: flex;
color: $black;
margin-top: 5px;
@@ -691,16 +685,19 @@
padding-right: 5px;
padding-top: 3;
padding-bottom: 3;
+ opacity: 0.8;
}
.presBox-dropdownOption:hover {
position: relative;
- background-color: lightgrey;
+ opacity: 1;
+ font-weight: bold;
}
.presBox-dropdownOption.active {
position: relative;
- background-color: $light-blue;
+ opacity: 1;
+ font-weight: bold;
}
.presBox-dropdownOptions {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index f750392f5..383b400c8 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -13,7 +13,7 @@ import { listSpec } from '../../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { AudioField } from '../../../../fields/URLField';
-import { emptyFunction, emptyPath, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils';
+import { emptyFunction, emptyPath, lightOrDark, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -28,6 +28,7 @@ import { CollectionFreeFormView, computeTimelineLayout, MarqueeViewBounds } from
import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline';
import { CollectionView } from '../../collections/CollectionView';
import { TabDocView } from '../../collections/TabDocView';
+import { TreeView } from '../../collections/TreeView';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { Colors } from '../../global/globalEnums';
import { LightboxView } from '../../LightboxView';
@@ -280,6 +281,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return listItems.filter(doc => !doc.layout_unrendered);
}
};
+
+ // go to documents chain
+ runSubroutines = (childrenToRun: Doc[], normallyNextSlide: Doc) => {
+ console.log(childrenToRun, normallyNextSlide, 'runSUBFUNC');
+ if (childrenToRun === undefined) {
+ console.log('children undefined');
+ return;
+ }
+ if (childrenToRun[0] === normallyNextSlide) {
+ return;
+ }
+
+ childrenToRun.forEach(child => {
+ DocumentManager.Instance.showDocument(child, {});
+ });
+ };
+
// Called when the user activates 'next' - to move to the next part of the pres. trail
@action
next = () => {
@@ -320,6 +338,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// Case 1: No more frames in current doc and next slide is defined, therefore move to next slide
const slides = DocListCast(this.rootDoc[StrCast(this.presFieldKey, 'data')]);
const curLast = this.selectedArray.size ? Math.max(...Array.from(this.selectedArray).map(d => slides.indexOf(DocCast(d)))) : this.itemIndex;
+
+ // before moving onto next slide, run the subroutines :)
+ const currentDoc = this.childDocs[this.itemIndex];
+ //could i do this.childDocs[this.itemIndex] for first arg?
+ this.runSubroutines(TreeView.GetRunningChildren.get(currentDoc)?.(), this.childDocs[this.itemIndex + 1]);
+
this.nextSlide(curLast + 1 === this.childDocs.length ? (this.layoutDoc.presLoop ? 0 : curLast) : curLast + 1);
progressiveReveal(true); // shows first progressive document, but without a transition effect
} else {
@@ -365,11 +389,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (from?.mediaStopTriggerList && this.layoutDoc.presentation_status !== PresStatus.Edit) {
DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia);
}
- if (from?.mediaStop === 'auto' && this.layoutDoc.presentation_status !== PresStatus.Edit) {
+ if (from?.presentation_mediaStop === 'auto' && this.layoutDoc.presentation_status !== PresStatus.Edit) {
this.stopTempMedia(from.presentation_targetDoc);
}
// If next slide is audio / video 'Play automatically' then the next slide should be played
- if (this.layoutDoc.presentation_status !== PresStatus.Edit && (this.targetDoc.type === DocumentType.AUDIO || this.targetDoc.type === DocumentType.VID) && this.activeItem.mediaStart === 'auto') {
+ if (this.layoutDoc.presentation_status !== PresStatus.Edit && (this.targetDoc.type === DocumentType.AUDIO || this.targetDoc.type === DocumentType.VID) && this.activeItem.presentation_mediaStart === 'auto') {
this.startTempMedia(this.targetDoc, this.activeItem);
}
if (!group) this.clearSelectedArray();
@@ -441,7 +465,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const fkey = Doc.LayoutFieldKey(bestTarget);
const setData = bestTargetView?.ComponentView?.setData;
if (setData) setData(activeItem.config_data);
- else Doc.GetProto(bestTarget)[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
+ else {
+ const current = Doc.GetProto(bestTarget)[fkey];
+ Doc.GetProto(bestTarget)[fkey + '_' + Date.now()] = current instanceof ObjectField ? current[Copy]() : current;
+ Doc.GetProto(bestTarget)[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
+ }
bestTarget[fkey + '_usePath'] = activeItem.config_usePath;
setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
}
@@ -711,6 +739,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
pinDoc.config_viewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
}
}
+
+ @action
+ static reversePin(pinDoc: Doc, targetDoc: Doc) {
+ // const fkey = Doc.LayoutFieldKey(targetDoc);
+ pinDoc.config_data = targetDoc.data;
+
+ console.log(pinDoc.presData);
+ }
+
/**
* This method makes sure that cursor navigates to the element that
* has the option open and last in the group.
@@ -763,8 +800,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any,
zoomTextSelections: BoolCast(activeItem.presentation_zoomText),
playAudio: BoolCast(activeItem.presPlayAudio),
+ playMedia: activeItem.presentation_mediaStart === 'auto',
};
- if (activeItem.presOpenInLightbox) {
+ if (activeItem.presentation_openInLightbox) {
const context = DocCast(targetDoc.annotationOn) ?? targetDoc;
if (!DocumentManager.Instance.getLightboxDocumentView(context)) {
LightboxView.SetLightboxDoc(context);
@@ -1040,8 +1078,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (doc.type === DocumentType.LABEL) {
const audio = Cast(doc.annotationOn, Doc, null);
if (audio) {
- audio.mediaStart = 'manual';
- audio.mediaStop = 'manual';
audio.config_clipStart = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */));
audio.config_clipEnd = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */));
audio.presentation_duration = audio.config_clipStart - audio.config_clipEnd;
@@ -1117,7 +1153,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (index >= 0 && index < this.childDocs.length) {
this.rootDoc._itemIndex = index;
}
- } else this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
+ } else {
+ this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
+ }
this.updateCurrentPresentation(DocCast(doc.embedContainer));
};
@@ -1340,7 +1378,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get paths() {
let pathPoints = '';
this.childDocs.forEach((doc, index) => {
- const tagDoc = Cast(doc.presentation_targetDoc, Doc, null);
+ const tagDoc = PresBox.targetRenderedDoc(doc);
if (tagDoc) {
const n1x = NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2;
const n1y = NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2;
@@ -1435,8 +1473,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@undoBatch
@action
updateOpenDoc = (activeItem: Doc) => {
- activeItem.presOpenInLightbox = !activeItem.presOpenInLightbox;
- this.selectedArray.forEach(doc => (doc.presOpenInLightbox = activeItem.presOpenInLightbox));
+ activeItem.presentation_openInLightbox = !activeItem.presentation_openInLightbox;
+ this.selectedArray.forEach(doc => (doc.presentation_openInLightbox = activeItem.presentation_openInLightbox));
};
@undoBatch
@@ -1455,6 +1493,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect)));
static _sliderBatch: any;
+ static endBatch = () => {
+ PresBox._sliderBatch.end();
+ document.removeEventListener('pointerup', PresBox.endBatch, true);
+ };
public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => {
return (
<input
@@ -1464,13 +1506,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
max={max}
value={value}
readOnly={true}
- style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)` }}
+ style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)`, background: SettingsManager.userColor, color: SettingsManager.userVariantColor }}
className={`toolbar-slider ${active ? '' : 'none'}`}
onPointerDown={e => {
PresBox._sliderBatch = UndoManager.StartBatch('pres slider');
+ document.addEventListener('pointerup', PresBox.endBatch, true);
e.stopPropagation();
}}
- onPointerUp={() => PresBox._sliderBatch.end()}
onChange={e => {
e.stopPropagation();
change(e.target.value);
@@ -1495,7 +1537,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
};
- @computed get visibiltyDurationDropdown() {
+ @computed get visibilityDurationDropdown() {
const activeItem = this.activeItem;
if (activeItem && this.targetDoc) {
const targetType = this.targetDoc.type;
@@ -1504,30 +1546,49 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return (
<div className="presBox-ribbon">
<div className="ribbon-doubleButton">
- <Tooltip title={<div className="dash-tooltip">{'Hide before presented'}</div>}>
- <div className={`ribbon-toggle ${activeItem.presentation_hideBefore ? 'active' : ''}`} onClick={() => this.updateHideBefore(activeItem)}>
+ <Tooltip title={<div className="dash-tooltip">Hide before presented</div>}>
+ <div
+ className={`ribbon-toggle ${activeItem.presentation_hideBefore ? 'active' : ''}`}
+ style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hideBefore ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+ onClick={() => this.updateHideBefore(activeItem)}>
Hide before
</div>
</Tooltip>
<Tooltip title={<div className="dash-tooltip">{'Hide while presented'}</div>}>
- <div className={`ribbon-toggle ${activeItem.presentation_hide ? 'active' : ''}`} onClick={() => this.updateHide(activeItem)}>
+ <div
+ className={`ribbon-toggle ${activeItem.presentation_hide ? 'active' : ''}`}
+ style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hide ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+ onClick={() => this.updateHide(activeItem)}>
Hide
</div>
</Tooltip>
<Tooltip title={<div className="dash-tooltip">{'Hide after presented'}</div>}>
- <div className={`ribbon-toggle ${activeItem.presentation_hideAfter ? 'active' : ''}`} onClick={() => this.updateHideAfter(activeItem)}>
+ <div
+ className={`ribbon-toggle ${activeItem.presentation_hideAfter ? 'active' : ''}`}
+ style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hideAfter ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+ onClick={() => this.updateHideAfter(activeItem)}>
Hide after
</div>
</Tooltip>
<Tooltip title={<div className="dash-tooltip">{'Open in lightbox view'}</div>}>
- <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presOpenInLightbox ? Colors.LIGHT_BLUE : '' }} onClick={() => this.updateOpenDoc(activeItem)}>
+ <div
+ className="ribbon-toggle"
+ style={{
+ border: `solid 1px ${SettingsManager.userColor}`,
+ color: SettingsManager.userColor,
+ background: activeItem.presentation_openInLightbox ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ }}
+ onClick={() => this.updateOpenDoc(activeItem)}>
Lightbox
</div>
</Tooltip>
- <Tooltip title={<div className="dash-tooltip">{'Transition movement style'}</div>}>
- <div className="ribbon-toggle" onClick={() => this.updateEaseFunc(activeItem)}>
+ <Tooltip title={<div className="dash-tooltip">Transition movement style</div>}>
+ <div
+ className="ribbon-toggle"
+ style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presEaseFunc === 'ease' ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+ onClick={() => this.updateEaseFunc(activeItem)}>
{`${StrCast(activeItem.presEaseFunc, 'ease')}`}
</div>
</Tooltip>
@@ -1536,10 +1597,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<>
<div className="ribbon-doubleButton">
<div className="presBox-subheading">Slide Duration</div>
- <div className="ribbon-property">
+ <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
<input className="presBox-input" type="number" readOnly={true} value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
</div>
- <div className="ribbon-propertyUpDown">
+ <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateDurationTime(String(duration), 1000)}>
<FontAwesomeIcon icon={'caret-up'} />
</div>
@@ -1578,7 +1639,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="presBox-subheading">Progressivize Collection</div>
<input
className="presBox-checkbox"
- style={{ margin: 10 }}
+ style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
type="checkbox"
onChange={() => {
activeItem.presentation_indexed = activeItem.presentation_indexed === undefined ? 0 : undefined;
@@ -1601,7 +1662,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="presBox-subheading">Progressivize First Bullet</div>
<input
className="presBox-checkbox"
- style={{ margin: 10 }}
+ style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
type="checkbox"
onChange={() => (activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1)}
checked={!NumCast(activeItem.presentation_indexedStart)}
@@ -1609,7 +1670,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
<div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
<div className="presBox-subheading">Expand Current Bullet</div>
- <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.presBulletExpand = !activeItem.presBulletExpand)} checked={BoolCast(activeItem.presBulletExpand)} />
+ <input
+ className="presBox-checkbox"
+ style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+ type="checkbox"
+ onChange={() => (activeItem.presBulletExpand = !activeItem.presBulletExpand)}
+ checked={BoolCast(activeItem.presBulletExpand)}
+ />
</div>
<div className="ribbon-box">
@@ -1620,10 +1687,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
e.stopPropagation();
this._openBulletEffectDropdown = !this._openBulletEffectDropdown;
})}
- style={{ borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5, border: this._openBulletEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
+ style={{
+ color: SettingsManager.userColor,
+ background: SettingsManager.userVariantColor,
+ borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5,
+ border: this._openBulletEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`,
+ }}>
{effect?.toString()}
<FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
- <div className={'presBox-dropdownOptions'} style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none' }} onPointerDown={e => e.stopPropagation()}>
+ <div
+ className={'presBox-dropdownOptions'}
+ style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none', color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}
+ onPointerDown={e => e.stopPropagation()}>
{bulletEffect(PresEffect.None)}
{bulletEffect(PresEffect.Fade)}
{bulletEffect(PresEffect.Flip)}
@@ -1654,7 +1729,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
);
const presDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => {
- const color = activeItem.presentation_effectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presentation_effectDirection) ? Colors.LIGHT_BLUE : 'black';
+ const color = activeItem.presentation_effectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presentation_effectDirection) ? SettingsManager.userVariantColor : SettingsManager.userColor;
return (
<Tooltip title={<div className="dash-tooltip">{direction}</div>}>
<div
@@ -1688,10 +1763,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
e.stopPropagation();
this._openMovementDropdown = !this._openMovementDropdown;
})}
- style={{ borderBottomLeftRadius: this._openMovementDropdown ? 0 : 5, border: this._openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
+ style={{
+ color: SettingsManager.userColor,
+ background: SettingsManager.userVariantColor,
+ borderBottomLeftRadius: this._openMovementDropdown ? 0 : 5,
+ border: this._openMovementDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`,
+ }}>
{this.movementName(activeItem)}
<FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
- <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={StopEvent} style={{ display: this._openMovementDropdown ? 'grid' : 'none' }}>
+ <div
+ className="presBox-dropdownOptions"
+ id={'presBoxMovementDropdown'}
+ onPointerDown={StopEvent}
+ style={{
+ color: SettingsManager.userColor,
+ background: SettingsManager.userBackgroundColor,
+ display: this._openMovementDropdown ? 'grid' : 'none',
+ }}>
{presMovement(PresMovement.None)}
{presMovement(PresMovement.Center)}
{presMovement(PresMovement.Zoom)}
@@ -1701,10 +1789,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
<div className="ribbon-doubleButton" style={{ display: activeItem.presentation_movement === PresMovement.Zoom ? 'inline-flex' : 'none' }}>
<div className="presBox-subheading">Zoom (% screen filled)</div>
- <div className="ribbon-property">
+ <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
<input className="presBox-input" type="number" readOnly={true} value={zoom} onChange={e => this.updateZoom(e.target.value)} />%
</div>
- <div className="ribbon-propertyUpDown">
+ <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), 0.1)}>
<FontAwesomeIcon icon={'caret-up'} />
</div>
@@ -1716,10 +1804,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
{PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)}
<div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
<div className="presBox-subheading">Transition Time</div>
- <div className="ribbon-property">
+ <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
<input className="presBox-input" type="number" readOnly={true} value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
</div>
- <div className="ribbon-propertyUpDown">
+ <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), 1000)}>
<FontAwesomeIcon icon={'caret-up'} />
</div>
@@ -1739,13 +1827,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
Effects
<div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
<div className="presBox-subheading">Play Audio Annotation</div>
- <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))} checked={BoolCast(activeItem.presPlayAudio)} />
+ <input
+ className="presBox-checkbox"
+ style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+ type="checkbox"
+ onChange={() => (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))}
+ checked={BoolCast(activeItem.presPlayAudio)}
+ />
</div>
<div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
<div className="presBox-subheading">Zoom Text Selections</div>
<input
className="presBox-checkbox"
- style={{ margin: 10 }}
+ style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
type="checkbox"
onChange={() => (activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText))}
checked={BoolCast(activeItem.presentation_zoomText)}
@@ -1757,10 +1851,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
e.stopPropagation();
this._openEffectDropdown = !this._openEffectDropdown;
})}
- style={{ borderBottomLeftRadius: this._openEffectDropdown ? 0 : 5, border: this._openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
+ style={{
+ color: SettingsManager.userColor,
+ background: SettingsManager.userVariantColor,
+ borderBottomLeftRadius: this._openEffectDropdown ? 0 : 5,
+ border: this._openEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`,
+ }}>
{effect?.toString()}
<FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
- <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this._openEffectDropdown ? 'grid' : 'none' }} onPointerDown={e => e.stopPropagation()}>
+ <div
+ className="presBox-dropdownOptions"
+ id={'presBoxMovementDropdown'}
+ style={{
+ color: SettingsManager.userColor,
+ background: SettingsManager.userBackgroundColor,
+ display: this._openEffectDropdown ? 'grid' : 'none',
+ }}
+ onPointerDown={e => e.stopPropagation()}>
{preseEffect(PresEffect.None)}
{preseEffect(PresEffect.Fade)}
{preseEffect(PresEffect.Flip)}
@@ -1771,7 +1878,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
<div className="ribbon-doubleButton" style={{ display: effect === PresEffectDirection.None ? 'none' : 'inline-flex' }}>
<div className="presBox-subheading">Effect direction</div>
- <div className="ribbon-property">{StrCast(this.activeItem.presentation_effectDirection)}</div>
+ <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
+ {StrCast(this.activeItem.presentation_effectDirection)}
+ </div>
</div>
<div className="effectDirection" style={{ display: effect === PresEffectDirection.None ? 'none' : 'grid', width: 40 }}>
{presDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})}
@@ -1793,8 +1902,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get mediaOptionsDropdown() {
const activeItem = this.activeItem;
if (activeItem && this.targetDoc) {
- const clipStart = NumCast(activeItem.clipStart);
- const clipEnd = NumCast(activeItem.clipEnd, NumCast(activeItem[Doc.LayoutFieldKey(activeItem) + '_duration']));
+ const renderTarget = PresBox.targetRenderedDoc(this.activeItem);
+ const clipStart = NumCast(renderTarget.clipStart);
+ const clipEnd = NumCast(renderTarget.clipEnd, clipStart + NumCast(renderTarget[Doc.LayoutFieldKey(renderTarget) + '_duration']));
+ const config_clipEnd = NumCast(activeItem.config_clipEnd) < NumCast(activeItem.config_clipStart) ? clipEnd - clipStart : NumCast(activeItem.config_clipEnd);
return (
<div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
<div>
@@ -1805,7 +1916,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="slider-text" style={{ fontWeight: 500 }}>
Start time (s)
</div>
- <div id={'startTime'} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
+ <div id="startTime" className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
<input
className="presBox-input"
style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
@@ -1813,9 +1924,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
readOnly={true}
value={NumCast(activeItem.config_clipStart).toFixed(2)}
onKeyDown={e => e.stopPropagation()}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
- activeItem.config_clipStart = Number(e.target.value);
- })}
+ onChange={action(e => (activeItem.config_clipStart = Number(e.target.value)))}
/>
</div>
</div>
@@ -1823,25 +1932,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="slider-text" style={{ fontWeight: 500 }}>
Duration (s)
</div>
- <div className="slider-number" style={{ backgroundColor: Colors.LIGHT_BLUE }}>
- {Math.round((NumCast(activeItem.config_clipEnd) - NumCast(activeItem.config_clipStart)) * 10) / 10}
+ <div className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
+ {Math.round((config_clipEnd - NumCast(activeItem.config_clipStart)) * 10) / 10}
</div>
</div>
<div className="slider-block">
<div className="slider-text" style={{ fontWeight: 500 }}>
End time (s)
</div>
- <div id={'endTime'} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
+ <div id="endTime" className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
<input
className="presBox-input"
onKeyDown={e => e.stopPropagation()}
style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
type="number"
readOnly={true}
- value={NumCast(activeItem.config_clipEnd).toFixed(2)}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
- activeItem.config_clipEnd = Number(e.target.value);
- })}
+ value={config_clipEnd.toFixed(2)}
+ onChange={action(e => (activeItem.config_clipEnd = Number(e.target.value)))}
/>
</div>
</div>
@@ -1852,16 +1959,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
step="0.1"
min={clipStart}
max={clipEnd}
- value={NumCast(activeItem.config_clipEnd)}
- style={{ gridColumn: 1, gridRow: 1 }}
+ value={config_clipEnd}
+ style={{ gridColumn: 1, gridRow: 1, background: SettingsManager.userColor, color: SettingsManager.userVariantColor }}
className={`toolbar-slider ${'end'}`}
id="toolbar-slider"
onPointerDown={e => {
this._batch = UndoManager.StartBatch('config_clipEnd');
const endBlock = document.getElementById('endTime');
if (endBlock) {
- endBlock.style.color = Colors.LIGHT_GRAY;
- endBlock.style.backgroundColor = Colors.MEDIUM_BLUE;
+ endBlock.style.backgroundColor = SettingsManager.userVariantColor;
}
e.stopPropagation();
}}
@@ -1869,8 +1975,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._batch?.end();
const endBlock = document.getElementById('endTime');
if (endBlock) {
- endBlock.style.color = Colors.BLACK;
- endBlock.style.backgroundColor = Colors.LIGHT_GRAY;
+ endBlock.style.backgroundColor = SettingsManager.userBackgroundColor;
}
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1891,8 +1996,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._batch = UndoManager.StartBatch('config_clipStart');
const startBlock = document.getElementById('startTime');
if (startBlock) {
- startBlock.style.color = Colors.LIGHT_GRAY;
- startBlock.style.backgroundColor = Colors.MEDIUM_BLUE;
+ startBlock.style.backgroundColor = SettingsManager.userVariantColor;
}
e.stopPropagation();
}}
@@ -1900,8 +2004,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._batch?.end();
const startBlock = document.getElementById('startTime');
if (startBlock) {
- startBlock.style.color = Colors.BLACK;
- startBlock.style.backgroundColor = Colors.LIGHT_GRAY;
+ startBlock.style.backgroundColor = SettingsManager.userBackgroundColor;
}
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1921,22 +2024,46 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="presBox-subheading">Start playing:</div>
<div className="presBox-radioButtons">
<div className="checkbox-container">
- <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStart = 'manual')} checked={activeItem.mediaStart === 'manual'} />
+ <input
+ className="presBox-checkbox"
+ type="checkbox"
+ style={{ border: `solid 1px ${SettingsManager.userColor}` }}
+ onChange={() => (activeItem.presentation_mediaStart = 'manual')}
+ checked={activeItem.presentation_mediaStart === 'manual'}
+ />
<div>On click</div>
</div>
<div className="checkbox-container">
- <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStart = 'auto')} checked={activeItem.mediaStart === 'auto'} />
+ <input
+ className="presBox-checkbox"
+ style={{ border: `solid 1px ${SettingsManager.userColor}` }}
+ type="checkbox"
+ onChange={() => (activeItem.presentation_mediaStart = 'auto')}
+ checked={activeItem.presentation_mediaStart === 'auto'}
+ />
<div>Automatically</div>
</div>
</div>
<div className="presBox-subheading">Stop playing:</div>
<div className="presBox-radioButtons">
<div className="checkbox-container">
- <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStop = 'manual')} checked={activeItem.mediaStop === 'manual'} />
- <div>At audio end time</div>
+ <input
+ className="presBox-checkbox"
+ type="checkbox"
+ style={{ border: `solid 1px ${SettingsManager.userColor}` }}
+ onChange={() => (activeItem.presentation_mediaStop = 'manual')}
+ checked={activeItem.presentation_mediaStop === 'manual'}
+ />
+ <div>At media end time</div>
</div>
<div className="checkbox-container">
- <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStop = 'auto')} checked={activeItem.mediaStop === 'auto'} />
+ <input
+ className="presBox-checkbox"
+ type="checkbox"
+ style={{ border: `solid 1px ${SettingsManager.userColor}` }}
+ onChange={() => (activeItem.presentation_mediaStop = 'auto')}
+ checked={activeItem.presentation_mediaStop === 'auto'}
+ />
<div>On slide change</div>
</div>
{/* <div className="checkbox-container">
@@ -2184,8 +2311,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
const mode = StrCast(this.rootDoc._type_collection) as CollectionViewType;
const isMini: boolean = this.toolbarWidth <= 100;
- const activeColor = Colors.LIGHT_BLUE;
- const inactiveColor = Colors.WHITE;
+ const activeColor = SettingsManager.userVariantColor;
+ const inactiveColor = lightOrDark(SettingsManager.userBackgroundColor) === Colors.WHITE ? Colors.WHITE : SettingsManager.userBackgroundColor;
return mode === CollectionViewType.Carousel3D || Doc.IsInMyOverlay(this.rootDoc) ? null : (
<div id="toolbarContainer" className={'presBox-toolbar'}>
{/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}>
@@ -2195,7 +2322,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<Tooltip title={<div className="dash-tooltip">View paths</div>}>
<div
style={{ opacity: this.childDocs.length > 1 ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }}
- className={'toolbar-button'}
+ className="toolbar-button"
onClick={this.childDocs.length > 1 ? () => this.togglePath() : undefined}>
<FontAwesomeIcon icon={'exchange-alt'} />
</div>
@@ -2527,7 +2654,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
removeDocument={returnFalse}
dontRegisterView={true}
focus={this.focusElement}
- scriptContext={this}
ScreenToLocalTransform={this.getTransform}
AddToMap={this.AddToMap}
RemFromMap={this.RemFromMap}
diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss
index 4f95f0c1f..9ac2b5a94 100644
--- a/src/client/views/nodes/trails/PresElementBox.scss
+++ b/src/client/views/nodes/trails/PresElementBox.scss
@@ -4,6 +4,10 @@ $light-background: #ececec;
$slide-background: #d5dce2;
$slide-active: #5b9fdd;
+.testingv2 {
+ background-color: red;
+}
+
.presItem-container {
cursor: grab;
display: flex;
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 529a5024f..82ed9e8d5 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -6,7 +6,7 @@ import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
import { Height, Width } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
-import { Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType } from '../../../documents/DocumentTypes';
@@ -25,6 +25,9 @@ import { PresBox } from './PresBox';
import './PresElementBox.scss';
import { PresMovement } from './PresEnums';
import React = require('react');
+import { TreeView } from '../../collections/TreeView';
+import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
+import { MultiToggle, Type } from 'browndash-components';
/**
* This class models the view a document added to presentation will have in the presentation.
* It involves some functionality for its buttons and options.
@@ -301,8 +304,19 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
activeItem.config_rotation = NumCast(targetDoc.rotation);
activeItem.config_width = NumCast(targetDoc.width);
activeItem.config_height = NumCast(targetDoc.height);
- activeItem.config_pinLayout = true;
+ activeItem.config_pinLayout = !activeItem.config_pinLayout;
+ // activeItem.config_pinLayout = true;
};
+
+ //wait i dont think i have to do anything here since by default it'll revert to the previously saved if I don't save
+ //so basically, don't have an onClick for this, just let it do nada for now
+ @undoBatch
+ @action
+ revertToPreviouslySaved = (presTargetDoc: Doc, activeItem: Doc) => {
+ const target = DocCast(activeItem.annotationOn) ?? activeItem;
+ PresBox.reversePin(activeItem, target);
+ };
+
/**
* Method called for updating the view of the currently selected document
*
@@ -399,6 +413,18 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
};
+ @undoBatch
+ @action
+ lfg = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ // TODO: fix this bug
+ const { toggleChildrenRun } = this.rootDoc;
+ TreeView.ToggleChildrenRun.get(this.rootDoc)?.();
+
+ // call this.rootDoc.recurChildren() to get all the children
+ // if (iconClick) PresElementBox.showVideo = false;
+ };
+
@computed
get toolbarWidth(): number {
const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox);
@@ -409,13 +435,15 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@computed get presButtons() {
- const presBox = this.presBox; //presBox
- const presBoxColor: string = StrCast(presBox?._backgroundColor);
- const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
- const targetDoc: Doc = this.targetDoc;
- const activeItem: Doc = this.rootDoc;
+ const presBox = this.presBox;
+ const presBoxColor = StrCast(presBox?._backgroundColor);
+ const presColorBool = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
+ const targetDoc = this.targetDoc;
+ const activeItem = this.rootDoc;
+ const hasChildren = BoolCast(this.rootDoc?.hasChildren);
const items: JSX.Element[] = [];
+
items.push(
<Tooltip key="slide" title={<div className="dash-tooltip">Update captured doc layout</div>}>
<div
@@ -486,6 +514,22 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</Tooltip>
);
+ if (!Doc.noviceMode && hasChildren) {
+ // TODO: replace with if treeveiw, has childrenDocs
+ items.push(
+ <Tooltip key="children" title={<div className="dash-tooltip">Run child processes (tree only)</div>}>
+ <div
+ className="slideButton"
+ onClick={e => {
+ e.stopPropagation();
+ this.lfg(e);
+ }}
+ style={{ fontWeight: 700 }}>
+ <FontAwesomeIcon icon={'circle-play'} onPointerDown={e => e.stopPropagation()} />
+ </div>
+ </Tooltip>
+ );
+ }
items.push(
<Tooltip key="trash" title={<div className="dash-tooltip">Remove from presentation</div>}>
<div className={'slideButton'} onClick={this.removePresentationItem}>
@@ -532,7 +576,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
) : (
<div
ref={this._dragRef}
- className={`presItem-slide ${isCurrent ? 'active' : ''}`}
+ className={`presItem-slide ${isCurrent ? 'active' : ''}${this.rootDoc.runProcess ? ' testingv2' : ''}`}
style={{
display: 'infline-block',
backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 6a8b06377..18cf633f4 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -3,14 +3,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import { EditorView } from 'prosemirror-view';
import { ColorState } from 'react-color';
import { Doc, Opt } from '../../../fields/Doc';
-import { StrCast } from '../../../fields/Types';
import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from '../../../Utils';
import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT';
import { DocumentType } from '../../documents/DocumentTypes';
import { SelectionManager } from '../../util/SelectionManager';
+import { SettingsManager } from '../../util/SettingsManager';
import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
import { LinkPopup } from '../linking/LinkPopup';
import './AnchorMenu.scss';
@@ -21,62 +20,17 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
static Instance: AnchorMenu;
private _disposer: IReactionDisposer | undefined;
- private _disposer2: IReactionDisposer | undefined;
- private _commentCont = React.createRef<HTMLButtonElement>();
- private _palette = [
- 'rgba(208, 2, 27, 0.8)',
- 'rgba(238, 0, 0, 0.8)',
- 'rgba(245, 166, 35, 0.8)',
- 'rgba(248, 231, 28, 0.8)',
- 'rgba(245, 230, 95, 0.616)',
- 'rgba(139, 87, 42, 0.8)',
- 'rgba(126, 211, 33, 0.8)',
- 'rgba(65, 117, 5, 0.8)',
- 'rgba(144, 19, 254, 0.8)',
- 'rgba(238, 169, 184, 0.8)',
- 'rgba(224, 187, 228, 0.8)',
- 'rgba(225, 223, 211, 0.8)',
- 'rgba(255, 255, 255, 0.8)',
- 'rgba(155, 155, 155, 0.8)',
- 'rgba(0, 0, 0, 0.8)',
- ];
+ private _commentRef = React.createRef<HTMLDivElement>();
+ private _cropRef = React.createRef<HTMLDivElement>();
@observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)';
@observable public Status: 'marquee' | 'annotation' | '' = '';
// GPT additions
- @observable private GPTMode: GPTPopupMode = GPTPopupMode.SUMMARY;
@observable private selectedText: string = '';
- @observable private editorView?: EditorView;
- @observable private textDoc?: Doc;
- @observable private highlightRange: number[] | undefined;
- private selectionRange: number[] | undefined;
-
- @action
- setGPTMode = (mode: GPTPopupMode) => {
- this.GPTMode = mode;
- };
-
- @action
- setHighlightRange(r: number[] | undefined) {
- this.highlightRange = r;
- }
-
@action
- public setSelectedText = (txt: string) => {
- this.selectedText = txt;
- };
-
- @action
- public setEditorView = (editor: EditorView) => {
- this.editorView = editor;
- };
-
- @action
- public setTextDoc = (textDoc: Doc) => {
- this.textDoc = textDoc;
- };
+ public setSelectedText = (txt: string) => (this.selectedText = txt);
public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
@@ -105,20 +59,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
componentWillUnmount() {
this._disposer?.();
- this._disposer2?.();
}
componentDidMount() {
- this._disposer2 = reaction(
- () => this._opacity,
- opacity => {},
- { fireImmediately: true }
- );
this._disposer = reaction(
() => SelectionManager.Views().slice(),
- selected => {
- AnchorMenu.Instance.fadeOut(true);
- }
+ selected => AnchorMenu.Instance.fadeOut(true)
);
}
@@ -129,17 +75,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
gptSummarize = async (e: React.PointerEvent) => {
// move this logic to gptpopup, need to implement generate again
GPTPopup.Instance.setVisible(true);
- this.setHighlightRange(undefined);
GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
GPTPopup.Instance.setLoading(true);
try {
const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY);
- if (res) {
- GPTPopup.Instance.setText(res);
- } else {
- GPTPopup.Instance.setText('Something went wrong.');
- }
+ GPTPopup.Instance.setText(res || 'Something went wrong.');
} catch (err) {
console.error(err);
}
@@ -151,7 +92,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
this,
e,
(e: PointerEvent) => {
- this.StartDrag(e, this._commentCont.current!);
+ this.StartDrag(e, this._commentRef.current!);
return true;
},
returnFalse,
@@ -168,7 +109,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
this,
e,
(e: PointerEvent) => {
- this.StartCropDrag(e, this._commentCont.current!);
+ this.StartCropDrag(e, this._cropRef.current!);
return true;
},
returnFalse,
@@ -190,7 +131,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
tooltip={'Click to Highlight'}
onClick={this.highlightClicked}
colorPicker={this.highlightColor}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
<ColorPicker selectedColor={this.highlightColor} setFinalColor={this.changeHighlightColor} setSelectedColor={this.changeHighlightColor} size={Size.XSMALL} />
</Group>
@@ -214,44 +155,28 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
* all selected text available to summarize but its only supported for pdf and web ATM.
* @returns Whether the GPT icon for summarization should appear
*/
- canSummarize = (): boolean => {
- const docs = SelectionManager.Docs();
- if (docs.length > 0) {
- return docs.some(doc => doc.type === DocumentType.PDF || doc.type === DocumentType.WEB);
- }
- return false;
- };
-
- /**
- * Returns whether the selected text can be edited.
- * @returns Whether the GPT icon for summarization should appear
- */
- canEdit = (): boolean => {
- const docs = SelectionManager.Docs();
- if (docs.length > 0) {
- return docs.some(doc => doc.type === 'rtf');
- }
- return false;
- };
+ canSummarize = () => SelectionManager.Docs().some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any));
render() {
const buttons =
this.Status === 'marquee' ? (
<>
{this.highlighter}
- <IconButton
- tooltip="Drag to Place Annotation" //
- onPointerDown={this.pointerDown}
- icon={<FontAwesomeIcon icon="comment-alt" />}
- color={StrCast(Doc.UserDoc().userColor)}
- />
+ <div ref={this._commentRef}>
+ <IconButton
+ tooltip="Drag to Place Annotation" //
+ onPointerDown={this.pointerDown}
+ icon={<FontAwesomeIcon icon="comment-alt" />}
+ color={SettingsManager.userColor}
+ />
+ </div>
{/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/}
{AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
<IconButton
tooltip="Summarize with AI" //
onPointerDown={this.gptSummarize}
icon={<FontAwesomeIcon icon="comment-dots" size="lg" />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
)}
{AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
@@ -259,7 +184,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
tooltip="Click to Record Annotation" //
onPointerDown={this.audioDown}
icon={<FontAwesomeIcon icon="microphone" />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
)}
<Popup
@@ -267,15 +192,17 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
type={Type.PRIM}
icon={<FontAwesomeIcon icon={'search'} />}
popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
{AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : (
- <IconButton
- tooltip="Click/Drag to create cropped image" //
- onPointerDown={this.cropDown}
- icon={<FontAwesomeIcon icon="image" />}
- color={StrCast(Doc.UserDoc().userColor)}
- />
+ <div ref={this._cropRef}>
+ <IconButton
+ tooltip="Click/Drag to create cropped image" //
+ onPointerDown={this.cropDown}
+ icon={<FontAwesomeIcon icon="image" />}
+ color={SettingsManager.userColor}
+ />
+ </div>
)}
</>
) : (
@@ -285,7 +212,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
tooltip="Remove Link Anchor" //
onPointerDown={this.Delete}
icon={<FontAwesomeIcon icon="trash-alt" />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
)}
{this.PinToPres !== returnFalse && (
@@ -293,7 +220,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
tooltip="Pin to Presentation" //
onPointerDown={this.PinToPres}
icon={<FontAwesomeIcon icon="map-pin" />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
)}
{this.ShowTargetTrail !== returnFalse && (
@@ -301,7 +228,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
tooltip="Show Linked Trail" //
onPointerDown={this.ShowTargetTrail}
icon={<FontAwesomeIcon icon="taxi" />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
)}
{this.IsTargetToggler !== returnFalse && (
@@ -312,7 +239,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
toggleStatus={this.IsTargetToggler()}
onClick={this.MakeTargetToggle}
icon={<FontAwesomeIcon icon="thumbtack" />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
)}
</>
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 8bf626d73..038c45582 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -88,7 +88,6 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
this.sidebarId = id;
};
-
@observable
private imgTargetDoc: Doc | undefined;
@action
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index c3027f51f..23dc084ad 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -67,7 +67,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
private _pdfViewer: any;
private _styleRule: any; // stylesheet rule for making hyperlinks clickable
private _retries = 0; // number of times tried to create the PDF viewer
- private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
+ private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
private _setBrushViewer: undefined | ((view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void);
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -82,9 +82,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
private _ignoreScroll = false;
private _initialScroll: { loc: Opt<number>; easeFunc: 'linear' | 'ease' | undefined } | undefined;
private _forcedScroll = true;
- get _getAnchor() {
- return AnchorMenu.Instance?.GetAnchor;
- }
+ _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
selectionText = () => this._selectionText;
selectionContent = () => this._selectionContent;
@@ -375,7 +373,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
this._downY = e.clientY;
if ((this.props.Document._freeform_scale || 1) !== 1) return;
if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) {
- this._setPreviewCursor?.(e.clientX, e.clientY, true, false);
+ this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this.props.Document);
}
if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
this.props.select(false);
@@ -400,6 +398,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
@action
finishMarquee = (x?: number, y?: number) => {
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this.isAnnotating = false;
this._marqueeing = undefined;
this._textSelecting = true;
@@ -464,12 +463,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
onClick = (e: React.MouseEvent) => {
this._scrollStopper?.();
if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
- this._setPreviewCursor(e.clientX, e.clientY, false, false);
+ this._setPreviewCursor(e.clientX, e.clientY, false, false, this.props.Document);
}
// e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks
};
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void) => (this._setBrushViewer = func);
@action
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 6b6b0f2ff..aa46e57dc 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -453,7 +453,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
</div>
{resultsJSX.length > 0 && (
<div className="searchBox-results-container">
- <div className="section-header" style={{ background: StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE) }}>
+ <div className="section-header" style={{ background: SettingsManager.userVariantColor }}>
<div className="section-title">Results</div>
<div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
</div>
@@ -462,7 +462,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
)}
{recommendationsJSX.length > 0 && (
<div className="searchBox-recommendations-container">
- <div className="section-header" style={{ background: StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE) }}>
+ <div className="section-header" style={{ background: SettingsManager.userVariantColor }}>
<div className="section-title">Recommendations</div>
<div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
</div>
diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx
index 955a4a174..2139919e0 100644
--- a/src/client/views/selectedDoc/SelectedDocView.tsx
+++ b/src/client/views/selectedDoc/SelectedDocView.tsx
@@ -1,12 +1,14 @@
import React = require('react');
-import { Doc } from "../../../fields/Doc";
-import { observer } from "mobx-react";
-import { computed } from "mobx";
-import { StrCast } from "../../../fields/Types";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Colors, ListBox } from 'browndash-components';
+import { ListBox } from 'browndash-components';
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../fields/Doc';
+import { StrCast } from '../../../fields/Types';
import { DocumentManager } from '../../util/DocumentManager';
import { DocFocusOptions } from '../nodes/DocumentView';
+import { emptyFunction } from '../../../Utils';
+import { SettingsManager } from '../../util/SettingsManager';
export interface SelectedDocViewProps {
selectedDocs: Doc[];
@@ -14,34 +16,33 @@ export interface SelectedDocViewProps {
@observer
export class SelectedDocView extends React.Component<SelectedDocViewProps> {
-
@computed get selectedDocs() {
return this.props.selectedDocs;
}
-
render() {
- return <div className={`selectedDocView-container`}>
- <ListBox
- items={this.selectedDocs.map((doc) => {
- const icon = Doc.toIcon(doc);
- const iconEle = <FontAwesomeIcon size={'1x'} icon={icon} />;
- const text = StrCast(doc.title)
- const finished = () => {
-
- };
- const options: DocFocusOptions = {
- playAudio: false,
- };
- return {
- text: text,
- val: StrCast(doc._id),
- icon: iconEle,
- onClick: () => {DocumentManager.Instance.showDocument(doc, options, finished);}
- }
- })}
- color={StrCast(Doc.UserDoc().userColor)}
- />
- </div>
+ return (
+ <div className="selectedDocView-container">
+ <ListBox
+ items={this.selectedDocs.map(doc => {
+ const options: DocFocusOptions = {
+ playAudio: false,
+ playMedia: false,
+ willPan: true,
+ };
+ return {
+ text: StrCast(doc.title),
+ val: StrCast(doc._id),
+ color: SettingsManager.userColor,
+ background: SettingsManager.userBackgroundColor,
+ icon: <FontAwesomeIcon size={'1x'} icon={Doc.toIcon(doc)} />,
+ onClick: () => DocumentManager.Instance.showDocument(doc, options, emptyFunction),
+ };
+ })}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userBackgroundColor}
+ />
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 5b097e639..55d94406a 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -1,13 +1,14 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, IconButton, isDark, Size, Toggle, Type } from 'browndash-components';
+import { Button, IconButton, isDark, Size, Type } from 'browndash-components';
import { action, computed, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { FaBug, FaCamera } from 'react-icons/fa';
+import { FaBug } from 'react-icons/fa';
import { Doc, DocListCast } from '../../../fields/Doc';
import { AclAdmin, DashVersion } from '../../../fields/DocSymbols';
import { StrCast } from '../../../fields/Types';
import { GetEffectiveAcl } from '../../../fields/util';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
import { DocumentManager } from '../../util/DocumentManager';
import { PingManager } from '../../util/PingManager';
@@ -15,12 +16,14 @@ import { ReportManager } from '../../util/reportManager/ReportManager';
import { ServerStats } from '../../util/ServerStats';
import { SettingsManager } from '../../util/SettingsManager';
import { SharingManager } from '../../util/SharingManager';
-import { UndoManager } from '../../util/UndoManager';
+import { Transform } from '../../util/Transform';
import { CollectionDockingView } from '../collections/CollectionDockingView';
+import { CollectionLinearView } from '../collections/collectionLinear';
import { ContextMenu } from '../ContextMenu';
import { DashboardView } from '../DashboardView';
import { Colors } from '../global/globalEnums';
-import { DocumentView } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
+import { DefaultStyleProvider } from '../StyleProvider';
import './TopBar.scss';
/**
@@ -91,6 +94,43 @@ export class TopBar extends React.Component {
);
}
+ @computed get dashMenuButtons() {
+ const selDoc = Doc.MyTopBarBtns;
+ return !(selDoc instanceof Doc) ? null : (
+ <div className="collectionMenu-contMenuButtons" style={{ height: '100%' }}>
+ <CollectionLinearView
+ Document={selDoc}
+ DataDoc={undefined}
+ fieldKey="data"
+ dropAction="embed"
+ setHeight={returnFalse}
+ styleProvider={DefaultStyleProvider}
+ rootSelected={returnTrue}
+ bringToFront={emptyFunction}
+ select={emptyFunction}
+ isContentActive={returnTrue}
+ isAnyChildContentActive={returnFalse}
+ isSelected={returnFalse}
+ docViewPath={returnEmptyDoclist}
+ moveDocument={returnFalse}
+ addDocument={returnFalse}
+ addDocTab={DocumentViewInternal.addDocTabFunc}
+ pinToPres={emptyFunction}
+ removeDocument={returnFalse}
+ ScreenToLocalTransform={Transform.Identity}
+ PanelWidth={() => 200}
+ PanelHeight={() => 30}
+ renderDepth={0}
+ focus={emptyFunction}
+ whenChildContentsActiveChanged={emptyFunction}
+ childFilters={returnEmptyFilter}
+ childFiltersByRanges={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ />
+ </div>
+ );
+ }
+
/**
* Returns the center of the topbar
* This part of the topbar contains everything related to the current dashboard including:
@@ -118,23 +158,10 @@ export class TopBar extends React.Component {
style={{ fontWeight: 700, fontSize: '1rem' }}
onClick={(e: React.MouseEvent) => {
const dashView = Doc.ActiveDashboard && DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard);
- ContextMenu.Instance.addItem({ description: 'Open Dashboard View', event: this.navigateToHome, icon: 'edit' });
dashView?.showContextMenu(e.clientX + 20, e.clientY + 30);
}}
/>
- {!Doc.noviceMode && (
- <IconButton
- tooltip="Work on a copy of the dashboard layout"
- size={Size.SMALL}
- color={this.color}
- onClick={async () => {
- const batch = UndoManager.StartBatch('snapshot');
- await DashboardView.snapshotDashboard();
- batch.end();
- }}
- icon={<FaCamera />}
- />
- )}
+ {!Doc.noviceMode && this.dashMenuButtons}
</div>
) : null;
}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ad0e548ed..7170be6cc 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -20,6 +20,7 @@ import {
AclPrivate,
AclReadonly,
Animation,
+ AudioPlay,
CachedUpdates,
DirectLinks,
DocAcl,
@@ -220,6 +221,9 @@ export class Doc extends RefField {
public static get MyContextMenuBtns() {
return DocCast(Doc.UserDoc().myContextMenuBtns);
}
+ public static get MyTopBarBtns() {
+ return DocCast(Doc.UserDoc().myTopBarBtns);
+ }
public static get MyRecentlyClosed() {
return DocCast(Doc.UserDoc().myRecentlyClosed);
}
@@ -346,6 +350,7 @@ export class Doc extends RefField {
@observable public [DocAcl]: { [key: string]: symbol } = {};
@observable public [DocCss]: number = 0; // incrementer denoting a change to CSS layout
@observable public [DirectLinks] = new ObservableSet<Doc>();
+ @observable public [AudioPlay]: any; // meant to store sound object from Howl
@observable public [Animation]: Opt<Doc>;
@observable public [Highlight]: boolean = false;
static __Anim(Doc: Doc) {
@@ -399,6 +404,12 @@ export class Doc extends RefField {
public static set noviceMode(val) {
Doc.UserDoc().noviceMode = val;
}
+ public static get IsSharingEnabled() {
+ return Doc.UserDoc().isSharingEnabled as boolean;
+ }
+ public static set IsSharingEnabled(val) {
+ Doc.UserDoc().isSharingEnabled = val;
+ }
public static get defaultAclPrivate() {
return Doc.UserDoc().defaultAclPrivate;
}
@@ -842,16 +853,24 @@ export namespace Doc {
export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
const { clone, map, linkMap } = await Doc.MakeClone(doc);
const proms = new Set<string>();
- function replacer(key: any, value: any) {
+ function replacer(key: any, value: any) {
if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined;
- if (value instanceof ImageField) {
- const extension = value.url.href.replace(/.*\./, '');
- proms.add(value.url.href.replace('.' + extension, '_o.' + extension));
- return SerializationHelper.Serialize(value);
+ if (value?.__type === 'image') {
+ const extension = value.url.replace(/.*\./, '');
+ proms.add(value.url.replace('.' + extension, '_o.' + extension));
+ return SerializationHelper.Serialize(new ImageField(value.url));
}
- if (value instanceof PdfField || value instanceof AudioField || value instanceof VideoField) {
- proms.add(value.url.href);
- return SerializationHelper.Serialize(value);
+ if (value?.__type === 'pdf') {
+ proms.add(value.url);
+ return SerializationHelper.Serialize(new PdfField(value.url));
+ }
+ if (value?.__type === 'audio') {
+ proms.add(value.url);
+ return SerializationHelper.Serialize(new AudioField(value.url));
+ }
+ if (value?.__type === 'video') {
+ proms.add(value.url);
+ return SerializationHelper.Serialize(new VideoField(value.url));
}
if (
value instanceof Doc ||
@@ -878,14 +897,15 @@ export namespace Doc {
const zip = new JSZip();
var count = 0;
- const promArr = Array.from(proms).filter(url => url.startsWith(window.location.origin));
+ const promArr = Array.from(proms).filter(url => url?.startsWith("/files")).map(url => url.replace("/",""))// window.location.origin));
+ console.log(promArr.length);
if (!promArr.length) {
zip.file('docs.json', jsonDocs);
zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
} else
promArr.forEach((url, i) => {
// loading a file and add it in a zip file
- JSZipUtils.getBinaryContent(url, (err: any, data: any) => {
+ JSZipUtils.getBinaryContent(window.location.origin+"/"+url, (err: any, data: any) => {
if (err) throw err; // or handle the error
// // Generate a directory within the Zip file structure
// const assets = zip.folder("assets");
@@ -1452,18 +1472,36 @@ export namespace Doc {
const isTransparent = (color: string) => color !== '' && DashColor(color).alpha() !== 1;
return isTransparent(StrCast(doc[key]));
}
+ if (key === '-linkedTo') {
+ // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("..."))
+ const allLinks = LinkManager.Instance.getAllRelatedLinks(doc);
+ const matchLink = (value: string, anchor: Doc) => {
+ const linkedToExp = value?.split('=');
+ if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value;
+ return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
+ };
+ // prettier-ignore
+ return (value === Doc.FilterNone && !allLinks.length) ||
+ (value === Doc.FilterAny && !!allLinks.length) ||
+ (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) ||
+ matchLink(value,DocCast(link.link_anchor_2)) ));
+ }
if (typeof value === 'string') {
value = value.replace(`,${Utils.noRecursionHack}`, '');
}
const fieldVal = doc[key];
+ // prettier-ignore
+ if ((value === Doc.FilterAny && fieldVal !== undefined) ||
+ (value === Doc.FilterNone && fieldVal === undefined)) {
+ return true;
+ }
if (Cast(fieldVal, listSpec('string'), []).length) {
- const vals = Cast(fieldVal, listSpec('string'), []);
+ const vals = StrListCast(fieldVal);
const docs = vals.some(v => (v as any) instanceof Doc);
if (docs) return value === Field.toString(fieldVal as Field);
return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
- const fieldStr = Field.toString(fieldVal as Field);
- return fieldStr.includes(value) || (value === String.fromCharCode(127) + '--undefined--' && fieldVal === undefined); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+ return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
export function deiconifyView(doc: Doc) {
@@ -1508,18 +1546,20 @@ export namespace Doc {
}
export const FilterSep = '::';
+ export const FilterAny = '--any--';
+ export const FilterNone = '--undefined--';
// filters document in a container collection:
// all documents with the specified value for the specified key are included/excluded
// based on the modifiers :"check", "x", undefined
- export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: 'removeAll' | 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: string, append: boolean = true) {
+ export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: string, append: boolean = true) {
if (!container) return;
const filterField = '_' + (fieldPrefix ? fieldPrefix + '_' : '') + 'childFilters';
const childFilters = StrListCast(container[filterField]);
runInAction(() => {
for (let i = 0; i < childFilters.length; i++) {
const fields = childFilters[i].split(FilterSep); // split key:value:modifier
- if (fields[0] === key && (fields[1] === value.toString() || modifiers === 'match' || modifiers === 'removeAll' || (fields[2] === 'match' && modifiers === 'remove'))) {
+ if (fields[0] === key && (fields[1] === value.toString() || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) {
if (fields[2] === modifiers && modifiers && fields[1] === value.toString()) {
if (toggle) modifiers = 'remove';
else return;
@@ -1531,7 +1571,7 @@ export namespace Doc {
}
if (!childFilters.length && modifiers === 'match' && value === undefined) {
container[filterField] = undefined;
- } else if (modifiers !== 'remove' && modifiers !== 'removeAll') {
+ } else if (modifiers !== 'remove') {
!append && (childFilters.length = 0);
childFilters.push(key + FilterSep + value + FilterSep + modifiers);
container[filterField] = new List<string>(childFilters);
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
index 856c377fa..df74cc9fe 100644
--- a/src/fields/DocSymbols.ts
+++ b/src/fields/DocSymbols.ts
@@ -3,6 +3,7 @@ export const Self = Symbol('DocSelf');
export const SelfProxy = Symbol('DocSelfProxy');
export const FieldKeys = Symbol('DocFieldKeys');
export const FieldTuples = Symbol('DocFieldTuples');
+export const AudioPlay = Symbol('DocAudioPlay');
export const Width = Symbol('DocWidth');
export const Height = Symbol('DocHeight');
export const Animation = Symbol('DocAnimation');
@@ -24,4 +25,4 @@ export const Initializing = Symbol('DocInitializing');
export const ForceServerWrite = Symbol('DocForceServerWrite');
export const CachedUpdates = Symbol('DocCachedUpdates');
-export const DashVersion = 'v0.5.7';
+export const DashVersion = 'v0.6.00';
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index c16a1c124..498bec6ed 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -588,11 +588,10 @@ export class MobileInterface extends React.Component {
const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, 'row');
- const toggleTheme = ScriptField.MakeScript(`self.colorScheme = self.colorScheme ? undefined: ${ColorScheme.Dark}}`);
const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
const cloneDashboard = ScriptField.MakeScript(`cloneDashboard()`);
- dashboardDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, cloneDashboard!]);
- dashboardDoc.contextMenuLabels = new List<string>(['Toggle Theme Colors', 'Toggle Comic Mode', 'New Dashboard Layout']);
+ dashboardDoc.contextMenuScripts = new List<ScriptField>([toggleComic!, cloneDashboard!]);
+ dashboardDoc.contextMenuLabels = new List<string>(['Toggle Comic Mode', 'New Dashboard Layout']);
Doc.AddDocToList(scens, 'data', dashboardDoc);
};
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index ebc9deab7..ea5d8cb33 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -64,6 +64,17 @@ export default class UploadManager extends ApiManager {
subscription: '/uploadFormData',
secureHandler: async ({ req, res }) => {
const form = new formidable.IncomingForm();
+ let fileguids = '';
+ let filesize = '';
+ form.on('field', (e: string, value: string) => {
+ if (e === 'fileguids') {
+ (fileguids = value).split(';').map(guid => DashUploadUtils.uploadProgress.set(guid, 'reading file'));
+ }
+ if (e === 'filesize') {
+ filesize = value;
+ }
+ });
+ form.on('progress', e => fileguids.split(';').map(guid => DashUploadUtils.uploadProgress.set(guid, `read:(${Math.round((100 * +e) / +filesize)}%) ${e} of ${filesize}`)));
form.keepExtensions = true;
form.uploadDir = pathToDirectory(Directory.parsed_files);
return new Promise<void>(resolve => {
@@ -102,11 +113,10 @@ export default class UploadManager extends ApiManager {
//req.readableBuffer.head.data
return new Promise<void>(async resolve => {
req.addListener('data', async args => {
- console.log(args);
const payload = String.fromCharCode.apply(String, args);
- const videoId = JSON.parse(payload).videoId;
+ const { videoId, overwriteId } = JSON.parse(payload);
const results: Upload.FileResponse[] = [];
- const result = await DashUploadUtils.uploadYoutube(videoId);
+ const result = await DashUploadUtils.uploadYoutube(videoId, overwriteId ?? videoId);
result && results.push(result);
_success(res, results);
resolve();
@@ -123,7 +133,7 @@ export default class UploadManager extends ApiManager {
req.addListener('data', args => {
const payload = String.fromCharCode.apply(String, args);
const videoId = JSON.parse(payload).videoId;
- _success(res, { progress: DashUploadUtils.QueryYoutubeProgress(videoId) });
+ _success(res, { progress: DashUploadUtils.QueryYoutubeProgress(videoId, req.user) });
resolve();
});
});
@@ -252,9 +262,33 @@ export default class UploadManager extends ApiManager {
zip.extractEntryTo(entry.entryName, publicDirectory, true, false);
createReadStream(pathname).pipe(createWriteStream(targetname));
if (extension !== '.pdf') {
- createReadStream(pathname).pipe(createWriteStream(targetname.replace('_o' + extension, '_s' + extension)));
- createReadStream(pathname).pipe(createWriteStream(targetname.replace('_o' + extension, '_m' + extension)));
- createReadStream(pathname).pipe(createWriteStream(targetname.replace('_o' + extension, '_l' + extension)));
+ const { pngs, jpgs } = AcceptableMedia;
+ const resizers = [
+ { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Small },
+ { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Medium },
+ { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Large },
+ ];
+ let isImage = false;
+ if (pngs.includes(extension)) {
+ resizers.forEach(element => {
+ element.resizer = element.resizer.png();
+ });
+ isImage = true;
+ } else if (jpgs.includes(extension)) {
+ resizers.forEach(element => {
+ element.resizer = element.resizer.jpeg();
+ });
+ isImage = true;
+ }
+ if (isImage) {
+ resizers.forEach(resizer => {
+ createReadStream(pathname)
+ .on('error', e => console.log('Resizing read:' + e))
+ .pipe(resizer.resizer)
+ .on('error', e => console.log('Resizing write: ' + e))
+ .pipe(createWriteStream(targetname.replace('_o' + extension, resizer.suffix + extension)).on('error', e => console.log('Resizing write: ' + e)));
+ });
+ }
}
unlink(pathname, () => {});
} catch (e) {
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index e5e15ce99..19cb3f240 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -135,35 +135,39 @@ export namespace DashUploadUtils {
};
}
- export function QueryYoutubeProgress(videoId: string) {
- return uploadProgress.get(videoId) ?? 'failed';
+ export function QueryYoutubeProgress(videoId: string, user?: Express.User) {
+ // console.log(`PROGRESS:${videoId}`, (user as any)?.email);
+ return uploadProgress.get(videoId) ?? 'pending data upload';
}
- let uploadProgress = new Map<string, string>();
+ export let uploadProgress = new Map<string, string>();
- export function uploadYoutube(videoId: string): Promise<Upload.FileResponse> {
+ export function uploadYoutube(videoId: string, overwriteId: string): Promise<Upload.FileResponse> {
return new Promise<Upload.FileResponse<Upload.FileInformation>>((res, rej) => {
const name = videoId;
const path = name.replace(/^-/, '__') + '.mp4';
const finalPath = serverPathToFile(Directory.videos, path);
if (existsSync(finalPath)) {
- uploadProgress.set(videoId, 'computing duration');
+ uploadProgress.set(overwriteId, 'computing duration');
exec(`yt-dlp -o ${finalPath} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any, stderr: any) => {
const time = Array.from(stdout.trim().split(':')).reverse();
const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0);
res(resolveExistingFile(name, finalPath, Directory.videos, 'video/mp4', duration, undefined));
});
} else {
- uploadProgress.set(videoId, 'starting download');
+ uploadProgress.set(overwriteId, 'starting download');
const ytdlp = spawn(`yt-dlp`, ['-o', path, `https://www.youtube.com/watch?v=${videoId}`, '--max-filesize', '100M', '-f', 'mp4']);
- ytdlp.stdout.on('data', (data: any) => !uploadProgress.get(videoId)?.includes('Aborting.') && uploadProgress.set(videoId, data.toString()));
+ ytdlp.stdout.on('data', (data: any) => uploadProgress.set(overwriteId, data.toString()));
let errors = '';
- ytdlp.stderr.on('data', (data: any) => (errors = data.toString()));
+ ytdlp.stderr.on('data', (data: any) => {
+ uploadProgress.set(overwriteId, 'error:' + data.toString());
+ errors = data.toString();
+ });
ytdlp.on('exit', function (code: any) {
- if (code || uploadProgress.get(videoId)?.includes('Aborting.')) {
+ if (code) {
res({
source: {
size: 0,
@@ -175,7 +179,7 @@ export namespace DashUploadUtils {
result: { name: 'failed youtube query', message: `Could not archive video. ${code ? errors : uploadProgress.get(videoId)}` },
});
} else {
- uploadProgress.set(videoId, 'computing duration');
+ uploadProgress.set(overwriteId, 'computing duration');
exec(`yt-dlp-o ${path} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any, stderr: any) => {
const time = Array.from(stdout.trim().split(':')).reverse();
const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0);
@@ -280,6 +284,7 @@ export namespace DashUploadUtils {
const fileKey = (await md5File(file.path)) + '.pdf';
const textFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
if (fExists(fileKey, Directory.pdfs) && fExists(textFilename, Directory.text)) {
+ fs.unlink(file.path, () => {});
return new Promise<Upload.FileResponse>(res => {
const textFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
const readStream = createReadStream(serverPathToFile(Directory.text, textFilename));
@@ -385,7 +390,7 @@ export namespace DashUploadUtils {
const resolved = (filename = `upload_${Utils.GenerateGuid()}.${ext}`);
if (usingAzure()) {
const response = await AzureManager.UploadBase64ImageBlob(resolved, data);
- source = `${AzureManager.BASE_STRING}/${resolved}`;
+ source = `${AzureManager.BASE_STRING}/${resolved}`;
} else {
const error = await new Promise<Error | null>(resolve => {
writeFile(serverPathToFile(Directory.images, resolved), data, 'base64', resolve);
@@ -596,11 +601,20 @@ export namespace DashUploadUtils {
const outputPath = path.resolve(outputDirectory, (writtenFiles[suffix] = InjectSize(outputFileName, suffix)));
await new Promise<void>(async (resolve, reject) => {
const source = streamProvider();
- let readStream: Stream = source instanceof Promise ? await source : source;
+ let readStream = source instanceof Promise ? await source : source;
+ let error = false;
if (resizer) {
- readStream = readStream.pipe(resizer.withMetadata());
+ readStream = readStream.pipe(resizer.withMetadata()).on('error', async args => {
+ error = true;
+ if (error) {
+ const source2 = streamProvider();
+ let readStream2: Stream | undefined;
+ readStream2 = source2 instanceof Promise ? await source2 : source2;
+ readStream2?.pipe(createWriteStream(outputPath)).on('error', resolve).on('close', resolve);
+ }
+ });
}
- readStream.pipe(createWriteStream(outputPath)).on('close', resolve).on('error', reject);
+ !error && readStream?.pipe(createWriteStream(outputPath)).on('error', resolve).on('close', resolve);
});
}
return writtenFiles;
@@ -628,7 +642,7 @@ export namespace DashUploadUtils {
initial = undefined;
}
return {
- resizer: initial,
+ resizer: suffix === '_o' ? undefined : initial,
suffix,
};
}),
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index ee32de152..ccb709453 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -121,11 +121,11 @@ function determineEnvironment() {
const label = isRelease ? 'release' : 'development';
console.log(`\nrunning server in ${color(label)} mode`);
- // swilkins: I don't think we need to read from ClientUtils.RELEASE anymore. Should be able to invoke process.env.RELEASE
- // on the client side, thanks to dotenv in webpack.config.js
- let clientUtils = fs.readFileSync('./src/client/util/ClientUtils.ts.temp', 'utf8');
- clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`;
- fs.writeFileSync('./src/client/util/ClientUtils.ts', clientUtils, 'utf8');
+ // // swilkins: I don't think we need to read from ClientUtils.RELEASE anymore. Should be able to invoke process.env.RELEASE
+ // // on the client side, thanks to dotenv in webpack.config.js
+ // let clientUtils = fs.readFileSync('./src/client/util/ClientUtils.ts.temp', 'utf8');
+ // clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`;
+ // fs.writeFileSync('./src/client/util/ClientUtils.ts', clientUtils, 'utf8');
return isRelease;
}
@@ -149,55 +149,78 @@ function registerAuthenticationRoutes(server: express.Express) {
function registerCorsProxy(server: express.Express) {
server.use('/corsProxy', async (req, res) => {
- //const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : '';
- let requrl = decodeURIComponent(req.url.substring(1));
- const qsplit = requrl.split('?q=');
- const newqsplit = requrl.split('&q=');
+ res.setHeader('Access-Control-Allow-Origin', '*');
+ res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE');
+ res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers'));
+ const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : '';
+ let requrlraw = decodeURIComponent(req.url.substring(1));
+ const qsplit = requrlraw.split('?q=');
+ const newqsplit = requrlraw.split('&q=');
if (qsplit.length > 1 && newqsplit.length > 1) {
const lastq = newqsplit[newqsplit.length - 1];
- requrl = qsplit[0] + '?q=' + lastq.split('&')[0] + '&' + qsplit[1].split('&')[1];
+ requrlraw = qsplit[0] + '?q=' + lastq.split('&')[0] + '&' + qsplit[1].split('&')[1];
+ }
+ const requrl = requrlraw.startsWith('/') ? referer + requrlraw : requrlraw;
+ // cors weirdness here...
+ // if the referer is a cors page and the cors() route (I think) redirected to /corsProxy/<path> and the requested url path was relative,
+ // then we redirect again to the cors referer and just add the relative path.
+ if (!requrl.startsWith('http') && req.originalUrl.startsWith('/corsProxy') && referer?.includes('corsProxy')) {
+ res.redirect(referer + (referer.endsWith('/') ? '' : '/') + requrl);
+ } else {
+ proxyServe(req, requrl, res);
}
- proxyServe(req, requrl, res);
});
}
function proxyServe(req: any, requrl: string, response: any) {
const htmlBodyMemoryStream = new (require('memorystream'))();
- var retrieveHTTPBody: any;
var wasinBrFormat = false;
const sendModifiedBody = () => {
const header = response.headers['content-encoding'];
- const httpsToCors = (match: any, href: string, offset: any, string: any) => `href="${resolvedServerUrl + '/corsProxy/http' + href}"`;
- if (header?.includes('gzip')) {
+ const refToCors = (match: any, tag: string, sym: string, href: string, offset: any, string: any) => `${tag}=${sym + resolvedServerUrl}/corsProxy/${href + sym}`;
+ const relpathToCors = (match: any, href: string, offset: any, string: any) => `="${resolvedServerUrl + '/corsProxy/' + decodeURIComponent(req.originalUrl.split('/corsProxy/')[1].match(/https?:\/\/[^\/]*/)?.[0] ?? '') + '/' + href}"`;
+ if (header) {
try {
const bodyStream = htmlBodyMemoryStream.read();
if (bodyStream) {
- const htmlInputText = wasinBrFormat ? Buffer.from(brotli.decompress(bodyStream)) : zlib.gunzipSync(bodyStream);
+ const htmlInputText = wasinBrFormat ? Buffer.from(brotli.decompress(bodyStream)) : header.includes('gzip') ? zlib.gunzipSync(bodyStream) : bodyStream;
const htmlText = htmlInputText
.toString('utf8')
.replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>')
- // .replace('<script', '<noscript')
- // .replace('</script', '</noscript')
- // .replace(/href="https?([^"]*)"/g, httpsToCors)
+ .replace(/(src|href)=([\'\"])(https?[^\2\n]*)\1/g, refToCors) // replace src or href='http(s)://...' or href="http(s)://.."
+ //.replace(/= *"\/([^"]*)"/g, relpathToCors)
.replace(/data-srcset="[^"]*"/g, '')
.replace(/srcset="[^"]*"/g, '')
.replace(/target="_blank"/g, '');
- response.send(zlib.gzipSync(htmlText));
+ response.send(header?.includes('gzip') ? zlib.gzipSync(htmlText) : htmlText);
} else {
- req.pipe(request(requrl)).pipe(response);
+ req.pipe(request(requrl))
+ .on('error', (e: any) => console.log('requrl ', e))
+ .pipe(response)
+ .on('error', (e: any) => console.log('response pipe error', e));
console.log('EMPTY body:' + req.url);
}
} catch (e) {
console.log('ERROR?: ', e);
}
} else {
- req.pipe(htmlBodyMemoryStream).pipe(response);
+ req.pipe(htmlBodyMemoryStream)
+ .on('error', (e: any) => console.log('html body memorystream error', e))
+ .pipe(response)
+ .on('error', (e: any) => console.log('html body memory stream response error', e));
}
};
- retrieveHTTPBody = () => {
- req.headers.cookie = '';
+ const retrieveHTTPBody = () => {
+ //req.headers.cookie = '';
req.pipe(request(requrl))
- .on('error', (e: any) => console.log(`Malformed CORS url: ${requrl}`, e))
+ .on('error', (e: any) => {
+ console.log(`CORS url error: ${requrl}`, e);
+ response.send(`<html><body bgcolor="red" link="006666" alink="8B4513" vlink="006666">
+ <title>Error</title>
+ <div align="center"><h1>Failed to load: ${requrl} </h1></div>
+ <p>${e}</p>
+ </body></html>`);
+ })
.on('response', (res: any) => {
res.headers;
const headers = Object.keys(res.headers);
@@ -220,16 +243,18 @@ function proxyServe(req: any, requrl: string, response: any) {
response.headers = response._headers = res.headers;
})
.on('end', sendModifiedBody)
- .pipe(htmlBodyMemoryStream);
+ .pipe(htmlBodyMemoryStream)
+ .on('error', (e: any) => console.log('http body pipe error', e));
};
retrieveHTTPBody();
}
function registerEmbeddedBrowseRelativePathHandler(server: express.Express) {
server.use('*', (req, res) => {
+ // res.setHeader('Access-Control-Allow-Origin', '*');
+ // res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE');
+ // res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers'));
const relativeUrl = req.originalUrl;
- // if (req.originalUrl === '/css/main.css' || req.originalUrl === '/favicon.ico') res.end();
- // else
if (!res.headersSent && req.headers.referer?.includes('corsProxy')) {
if (!req.user) res.redirect('/home'); // When no user is logged in, we interpret a relative URL as being a reference to something they don't have access to and redirect to /home
// a request for something by a proxied referrer means it must be a relative reference. So construct a proxied absolute reference here.
@@ -239,8 +264,8 @@ function registerEmbeddedBrowseRelativePathHandler(server: express.Express) {
const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ''); // the url of the referer without the proxy (e.g., : https://en.wikipedia.org/wiki/Engelbart)
const absoluteTargetBaseUrl = actualReferUrl.match(/https?:\/\/[^\/]*/)![0]; // the base of the original url (e.g., https://en.wikipedia.org)
const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>)
- if (relativeUrl.startsWith('//')) res.redirect('http:' + relativeUrl);
- else res.redirect(redirectedProxiedUrl);
+ const redirectUrl = relativeUrl.startsWith('//') ? 'http:' + relativeUrl : redirectedProxiedUrl;
+ res.redirect(redirectUrl);
} catch (e) {
console.log('Error embed: ', e);
}