diff --git a/src/components/annotations-view.js b/src/components/annotations-view.js
index ed99f56b..6a9130b6 100644
--- a/src/components/annotations-view.js
+++ b/src/components/annotations-view.js
@@ -6,6 +6,8 @@ import { FormattedMessage, useIntl } from 'react-intl';
import cx from 'classnames';
import { SidebarPreview } from './preview';
import { searchAnnotations } from '../lib/search';
+import { IconColor } from "./icons";
+import { annotationColors } from "../lib/colors";
function AnnotationsViewSearch({ query, onInput, onClear }) {
const intl = useIntl();
@@ -42,6 +44,34 @@ function AnnotationsViewSearch({ query, onInput, onClear }) {
);
}
+function Selector({ tags, colors, onClickTag, onClickColor }) {
+ const intl = useIntl();
+ return (
+
+ {colors.length > 1 &&
+ {colors.map((color, index) => (
+
onClickColor(color.color)}
+ >
+ ))}
+
}
+ {!!tags.length &&
+ {tags.map((tag, index) => (
+ onClickTag(tag.name)}
+ >{tag.name}
+ ))}
+
}
+
+ );
+}
+
// We get significant performance boost here because `props.annotation`
// reference is updated only when annotation data is updated
const Annotation = React.memo((props) => {
@@ -68,8 +98,10 @@ const Annotation = React.memo((props) => {
});
const AnnotationsView = React.memo(function (props) {
- const [filteredAnnotations, setFilteredAnnotations] = useState(null);
+ const [searchedAnnotations, setSearchedAnnotations] = useState(null);
const [query, setQuery] = useState('');
+ const [selectedTags, setSelectedTags] = useState([]);
+ const [selectedColors, setSelectedColors] = useState([]);
function getContainerNode() {
return document.getElementById('annotationsView');
@@ -78,10 +110,10 @@ const AnnotationsView = React.memo(function (props) {
function search(query) {
let { annotations } = props;
if (query) {
- setFilteredAnnotations(searchAnnotations(annotations, query));
+ setSearchedAnnotations(searchAnnotations(annotations, query));
}
else {
- setFilteredAnnotations(null);
+ setSearchedAnnotations(null);
}
}
@@ -95,50 +127,141 @@ const AnnotationsView = React.memo(function (props) {
search();
}
+ function handleTagClick(name) {
+ console.log('tag click', name)
+ if (selectedTags.includes(name)) {
+ setSelectedTags(selectedTags.filter(x => x !== name));
+ }
+ else {
+ setSelectedTags([...selectedTags, name]);
+ }
+ }
+
+ function handleColorClick(color) {
+ if (selectedColors.includes(color)) {
+ setSelectedColors(selectedColors.filter(x => x !== color));
+ }
+ else {
+ setSelectedColors([...selectedColors, color]);
+ }
+ }
let containerNode = getContainerNode();
- if (!containerNode) return null;
+ if (!containerNode) {
+ return null;
+ }
let { annotations } = props;
- if (filteredAnnotations) {
- let newFilteredAnnotations = [];
- for (let filteredAnnotation of filteredAnnotations) {
+ if (searchedAnnotations) {
+ let newSearchedAnnotations = [];
+ for (let filteredAnnotation of searchedAnnotations) {
let annotation = annotations.find(x => x.id === filteredAnnotation.id);
if (annotation) {
- newFilteredAnnotations.push(annotation);
+ newSearchedAnnotations.push(annotation);
}
}
- annotations = newFilteredAnnotations;
+ annotations = newSearchedAnnotations;
+ }
+
+ if (selectedTags.length || selectedColors.length) {
+ annotations = annotations.filter(x => x.tags.some(t => selectedTags.includes(t.name)) || selectedColors.includes(x.color));
}
+ let tags = {};
+ let colors = {};
+ for (let annotation of props.annotations) {
+ for (let tag of annotation.tags) {
+ if (!tags[tag.name]) {
+ tags[tag.name] = { ...tag };
+ tags[tag.name].selected = selectedTags.includes(tag.name);
+ tags[tag.name].inactive = true;
+ }
+ }
+ let color = annotation.color;
+ if (!colors[color]) {
+ let predefinedColor = annotationColors.find(x => x[1] === color);
+ colors[color] = {
+ color,
+ selected: selectedColors.includes(color),
+ inactive: true,
+ name: predefinedColor ? predefinedColor[0] : null
+ };
+ }
+ }
+
+ for (let annotation of annotations) {
+ for (let tag of annotation.tags) {
+ tags[tag.name].inactive = false;
+ }
+ colors[annotation.color].inactive = false;
+ }
+
+ let coloredTags = [];
+ for (let key in tags) {
+ let tag = tags[key];
+ if (tag.color) {
+ coloredTags.push(tag);
+ delete tags[key];
+ }
+ }
+
+ // Sort colored tags and place at beginning
+ coloredTags.sort((a, b) => {
+ return a.position - b.position;
+ });
+
+ let primaryColors = [];
+ for (let annotationColor of annotationColors) {
+ if (colors[annotationColor[1]]) {
+ primaryColors.push(colors[annotationColor[1]]);
+ delete colors[annotationColor[1]];
+ }
+ }
+
+ tags = Object.values(tags);
+ let collator = new Intl.Collator();
+ tags.sort(function (a, b) {
+ return collator.compare(a.tag, b.tag);
+ });
+ tags = [...coloredTags, ...tags];
+
+ colors = Object.values(colors);
+ colors = [...primaryColors, ...colors];
+
return ReactDOM.createPortal(
-
- {annotations.length
- ? annotations.map(annotation => (
-
- ))
- : !query.length &&
}
- ,
- containerNode
- );
+
+
+ {annotations.length
+ ? annotations.map(annotation => (
+
+ ))
+ : !query.length &&
}
+
+ {(!!tags.length || colors.length > 1) && }
+ , containerNode);
});
export default AnnotationsView;
diff --git a/src/components/icons.js b/src/components/icons.js
index 301372b4..3214085b 100644
--- a/src/components/icons.js
+++ b/src/components/icons.js
@@ -51,3 +51,21 @@ export function IconNoteLarge() {
);
}
+
+export function IconColor({ color }) {
+ return (
+
+ );
+}
diff --git a/src/demo-annotations.js b/src/demo-annotations.js
index 47c78223..2be83a5a 100644
--- a/src/demo-annotations.js
+++ b/src/demo-annotations.js
@@ -111,10 +111,9 @@ let annotations = [
tags: [{
name: 'programming languages',
color: '#ff0000'
- },
- {
- name: 'compiler'
- }],
+ }, {
+ name: 'compiler'
+ }],
id: 690898672271502,
dateModified: '2020-02-07T07:24:37.870Z',
authorName: '',
@@ -162,7 +161,13 @@ let annotations = [
},
text: 'Nested loops can be difficult to optimize for tracing VMs. In a na ̈ıve implementation, inner loops would become hot first, and the VM would start tracing there. ',
comment: 'A problem of nested loops',
- tags: [],
+ tags: [{
+ name: 'This is another tag'
+ }, {
+ name: 'Astrophysics'
+ }, {
+ name: 'Sleep patters and habits testing options'
+ }],
id: 674358779481824,
dateModified: '2020-02-07T07:23:50.968Z',
authorName: '',
diff --git a/src/index.web.js b/src/index.web.js
index ded1284e..a2103d6c 100644
--- a/src/index.web.js
+++ b/src/index.web.js
@@ -122,7 +122,7 @@ class ViewerInstance {
state: options.state,
location: options.location,
sidebarWidth: 240,
- sidebarOpen: false,
+ sidebarOpen: true,
bottomPlaceholderHeight: 0,
localizedStrings: {},
readOnly: options.readOnly
diff --git a/src/stylesheets/components/annotator/_annotations-view.scss b/src/stylesheets/components/annotator/_annotations-view.scss
index 03c808f9..d4b04f5d 100644
--- a/src/stylesheets/components/annotator/_annotations-view.scss
+++ b/src/stylesheets/components/annotator/_annotations-view.scss
@@ -3,7 +3,12 @@
//
#annotationsView {
- @extend %sidebar-view;
+ width: 100%;
+ height: 100%;
+ user-select: none;
+
+ display: flex;
+ flex-direction: column;
&:focus {
outline: none;
@@ -65,6 +70,92 @@
border: none;
}
}
+
+ .annotations {
+ overflow: auto;
+ padding: 4px 4px 0; // Firefox does not render padding-bottom
+ flex: 1;
+ //-webkit-overflow-scrolling: touch;
+
+ > *:last-child {
+ margin-bottom: 8px;
+ }
+ }
+
+ .selector {
+ padding: 8px 8px;
+ border-top: 1px solid #a9a9a9;
+ box-shadow: 0 0 0 1px rgba(0,0,0,0.05);
+ z-index: 2;
+ max-height: 120px;
+ overflow: auto;
+
+ .colors {
+ display: flex;
+
+ .color {
+ padding: 2px;
+ display: flex;
+ border-radius: 3px;
+ margin-left: 1px;
+
+ &:hover, &.selected {
+ background-color: #bcc4d2;
+ }
+
+ &.inactive {
+ svg {
+ opacity: 0.4;
+ }
+ }
+ }
+ }
+
+ .tags {
+ display: flex;
+ flex-wrap: wrap;
+
+ &:nth-child(2) {
+ margin-top: 4px;
+ }
+
+ .tag {
+ cursor: default;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: pre;
+ padding: 2px 4px;
+ margin-left: 1px;
+ margin-bottom: 1px;
+
+ border-radius: 6px;
+ border: 1px solid transparent;
+
+ &.color {
+ font-weight: bold;
+ }
+
+ &.selected {
+ color: white;
+ background: rgb(89, 139, 236);
+ }
+
+ &:hover {
+ background: rgb(187, 206, 241);
+ border: 1px solid rgb(109, 149, 224);
+ }
+
+ &:active {
+ color: white;
+ background: rgb(89, 139, 236);
+ }
+
+ &.inactive {
+ opacity: 0.4;
+ }
+ }
+ }
+ }
}
#measure {
diff --git a/src/stylesheets/themes/_light-darwin.scss b/src/stylesheets/themes/_light-darwin.scss
index 72028987..56515ecd 100644
--- a/src/stylesheets/themes/_light-darwin.scss
+++ b/src/stylesheets/themes/_light-darwin.scss
@@ -331,7 +331,7 @@ $area-tool-icon: "darwin/area";
$area-tool-icon-active: "darwin/area-white";
// Search
-$search-margin: 1px 9px 9px 9px;
+$search-margin: 1px 13px 9px 13px;
$search-height: 20px;
$search-bg: #bcc4d2;
$search-blurred-bg: #dbdbdb;