Skip to content

Commit

Permalink
Enable text highlight selection, deletion, and color change
Browse files Browse the repository at this point in the history
* enable text highlight selection, deletion, and color change

text highlight selection and color changing

highlight deletion partially working, need to resolve edge cases with highlight across marks

deselect highlight on document check-in

highlight deletion fix for highlights spanning other marks

turn off highlight select mode on document check-in

* check all in wip

* add check in all feature

* update canvas to latest

* upgrade to node 12.12.0

* upgrade ruby to 2.5.7

* upgrade ruby to 2.5.7
  • Loading branch information
akstuhl authored and NickLaiacona committed Nov 7, 2019
1 parent 49559a6 commit b0e7253
Show file tree
Hide file tree
Showing 16 changed files with 5,813 additions and 3,692 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ git_source(:github) do |repo_name|
"https://github.com/#{repo_name}.git"
end

ruby '2.5.1'
ruby '2.5.7'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.2.0'
Expand Down
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ DEPENDENCIES
tzinfo-data

RUBY VERSION
ruby 2.5.1p57
ruby 2.5.7p206

BUNDLED WITH
1.16.5
2.0.2
8 changes: 7 additions & 1 deletion app/controllers/projects_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class ProjectsController < ApplicationController
before_action :set_project, only: [:show, :update, :destroy, :search]
before_action :set_project, only: [:show, :update, :destroy, :search, :check_in]
before_action :validate_user_approved, only: [:create]

before_action only: [:update, :destroy] do
Expand Down Expand Up @@ -65,6 +65,12 @@ def search
render json: results.map { |result| result.to_obj }
end

# GET /projects/1/check_in
def check_in
checked_in_doc_ids = @project.check_in_all(current_user)
render json: { checked_in_docs: checked_in_doc_ids }
end

private
# Use callbacks to share common setup or constraints between actions.
def set_project
Expand Down
13 changes: 13 additions & 0 deletions app/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ def destroyer
}
end

def check_in_all(user)
checked_in_doc_ids = []
self.documents.each { |document|
if user.id == document.locked_by_id then
document.locked = false
document.locked_by_id = nil
document.save!
checked_in_doc_ids.push(document.id)
end
}
checked_in_doc_ids
end

def can_read
self.users.merge(UserProjectPermission.read)
end
Expand Down
22 changes: 11 additions & 11 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"canvas": "^1.6.10",
"canvas": "2.6.0",
"history": "^4.7.2",
"jsdom": "^11.8.0",
"material-ui": "^0.20.0",
"openseadragon": "^2.3.1",
"openseadragon-fabricjs-overlay": "git://github.com/performant-software/OpenseadragonFabricjsOverlay.git",
"prosemirror-commands": "^1.0.5",
"prosemirror-commands": "^1.0.8",
"prosemirror-example-setup": "^1.0.1",
"prosemirror-menu": "^1.0.4",
"prosemirror-model": "^1.2.0",
"prosemirror-schema-basic": "^1.0.0",
"prosemirror-schema-list": "^1.0.0",
"prosemirror-state": "^1.1.0",
"prosemirror-tables": "^0.8.1",
"prosemirror-transform": "^1.0.10",
"prosemirror-view": "^1.2.0",
"prosemirror-menu": "^1.0.5",
"prosemirror-model": "^1.7.1",
"prosemirror-schema-basic": "^1.0.1",
"prosemirror-schema-list": "^1.0.3",
"prosemirror-state": "^1.2.3",
"prosemirror-tables": "^0.9.5",
"prosemirror-transform": "^1.1.3",
"prosemirror-view": "^1.9.10",
"react": "^16.4.1",
"react-activestorage-provider": "^0.6.0",
"react-color": "^2.14.1",
Expand All @@ -30,7 +30,7 @@
"react-resizable": "^1.7.5",
"react-router-dom": "^4.2.2",
"react-router-redux": "^5.0.0-alpha.9",
"react-scripts": "2.0.3",
"react-scripts": "3.2.0",
"redux": "^3.7.2",
"redux-devtools-extension": "^2.13.2",
"redux-thunk": "^2.2.0",
Expand Down
121 changes: 84 additions & 37 deletions client/src/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import HTML5Backend from 'react-dnd-html5-backend';
import { DragDropContext } from 'react-dnd';
import { loadProject, updateProject, showSettings, hideSettings } from './modules/project';
import { loadProject, updateProject, showSettings, hideSettings, checkInAll } from './modules/project';
import { selectTarget, closeTarget, closeTargetRollover, promoteTarget } from './modules/annotationViewer';
import { closeDeleteDialog, confirmDeleteDialog, layoutOptions } from './modules/documentGrid';
import { closeDeleteDialog, confirmDeleteDialog, layoutOptions, updateSnackBar } from './modules/documentGrid';
import { selectHighlight } from './modules/textEditor';
import Dialog from 'material-ui/Dialog';
import Snackbar from 'material-ui/Snackbar';
import FlatButton from 'material-ui/FlatButton';
import Navigation from './Navigation';
import ProjectSettingsDialog from './ProjectSettingsDialog';
Expand Down Expand Up @@ -35,13 +37,30 @@ class Project extends Component {
}
}

selectTextHighlight(document_id, highlight_id) {
if (this.props.highlightSelectModes[document_id]) {
this.props.selectHighlight(document_id, highlight_id);
}
// if the clicked highlight is currently selected, don't proceed with the normal popover behavior to facilitate editing the highlighted text
else if (this.props.selectedHighlights[document_id] !== highlight_id) {
this.setFocusHighlight(document_id, highlight_id);
}
}

showRollover(document_id, highlight_id) {
// if the hovered highlight is currently selected, don't proceed with the normal popover behavior to facilitate editing the highlighted text
if (this.props.selectedHighlights[document_id] === highlight_id) return;
const existingPopover = this.props.selectedTargets.find( target => !target.rollover && target.uid === highlight_id )
if( !existingPopover ) {
this.activateRolloverTimer( () => {
const target = this.createTarget(document_id, highlight_id)
target.rollover = true
this.props.selectTarget(target);
if (target) {
target.rollover = true
this.props.selectTarget(target);
}
else {
console.log('tried to show rollover for a non-existent target', document_id, highlight_id);
}
})
}
}
Expand Down Expand Up @@ -88,6 +107,7 @@ class Project extends Component {

componentDidMount() {
window.setFocusHighlight = this.setFocusHighlight.bind(this);
window.selectTextHighlight = this.selectTextHighlight.bind(this);
window.showRollover = this.showRollover.bind(this);
window.hideRollover = this.hideRollover.bind(this);
if (this.props.match.params.slug !== 'new') {
Expand Down Expand Up @@ -116,45 +136,51 @@ class Project extends Component {
renderDialogLayers() {
return (
<div>
<LinkInspectorPopupLayer
targets={this.props.selectedTargets}
closeHandler={this.props.closeTarget}
mouseDownHandler={this.props.promoteTarget}
openDocumentIds={this.props.openDocumentIds}
writeEnabled={this.props.writeEnabled}
sidebarWidth={this.props.sidebarWidth}
<LinkInspectorPopupLayer
targets={this.props.selectedTargets}
closeHandler={this.props.closeTarget}
mouseDownHandler={this.props.promoteTarget}
openDocumentIds={this.props.openDocumentIds}
writeEnabled={this.props.writeEnabled}
sidebarWidth={this.props.sidebarWidth}
/>
<SearchResultsPopupLayer
openDocumentIds={this.props.openDocumentIds}
sidebarWidth={this.props.sidebarWidth}
<SearchResultsPopupLayer
openDocumentIds={this.props.openDocumentIds}
sidebarWidth={this.props.sidebarWidth}
/>
{ this.renderDeleteDialog() }
<ProjectSettingsDialog />
</div>
);
}

getSelectedHighlight(document_id) {
return this.props.selectedHighlights[document_id];
}

renderDocumentViewer = (document,index) => {
const key = `${document.id}-${document.timeOpened}`;
return (
<DocumentViewer
key={key}
index={index}
document_id={document.id}
timeOpened={document.timeOpened}
resourceName={document.title}
document_kind={document.document_kind}
content={document.content}
highlight_map={document.highlight_map}
image_thumbnail_urls={document.image_thumbnail_urls}
image_urls={document.image_urls}
linkInspectorAnchorClick={() => {this.setFocusHighlight(document.id);}}
writeEnabled={this.props.writeEnabled}
<DocumentViewer
key={key}
index={index}
document_id={document.id}
timeOpened={document.timeOpened}
resourceName={document.title}
document_kind={document.document_kind}
content={document.content}
highlight_map={document.highlight_map}
getHighlightMap={function() {return document.highlight_map;}}
image_thumbnail_urls={document.image_thumbnail_urls}
image_urls={document.image_urls}
linkInspectorAnchorClick={() => {this.setFocusHighlight(document.id);}}
writeEnabled={this.props.writeEnabled}
locked={document.locked}
lockedByUserName={document.locked_by_user_name}
lockedByMe={document.locked_by_me}
numRows={this.numRows}
firstTarget={document.firstTarget}
getSelectedHighlight={this.getSelectedHighlight.bind(this)}
/>
);
}
Expand All @@ -170,20 +196,20 @@ class Project extends Component {
}
this.numRows = newNumRows;

const gridInnerStyle = {
margin: `72px 8px 0 ${this.props.sidebarWidth + 8}px`,
display: 'flex',
flexWrap: 'wrap',
overflow: 'hidden'
const gridInnerStyle = {
margin: `72px 8px 0 ${this.props.sidebarWidth + 8}px`,
display: 'flex',
flexWrap: 'wrap',
overflow: 'hidden'
}

return (
<div
id='document-grid-main'
ref={el => {this.mainContainer = el;}}
onMouseMove={event => {this.mouseX = event.clientX; this.mouseY = event.clientY;}}
>
<div
>
<div
id='document-grid-inner'
style={gridInnerStyle}
>
Expand All @@ -193,6 +219,18 @@ class Project extends Component {
);
}

renderSnackbar() {
const { snackBarMessage, snackBarOpen, updateSnackBar } = this.props
return (
<Snackbar
open={ snackBarOpen }
message={ snackBarMessage ? snackBarMessage : ""}
autoHideDuration={2000}
onRequestClose={ () => { updateSnackBar(false,null) } }
/>
)
}

render() {
const { title, projectId, loading, adminEnabled, sidebarWidth, contentsChildren, openDocumentIds, writeEnabled } = this.props
return (
Expand All @@ -203,16 +241,18 @@ class Project extends Component {
onTitleChange={(event, newValue) => {this.props.updateProject(projectId, {title: newValue});}}
isLoading={loading}
/>
<TableOfContents
<TableOfContents
showSettings={adminEnabled}
settingsClick={this.props.showSettings}
checkInAllClick={ () => this.props.checkInAll(projectId) }
sidebarWidth={sidebarWidth}
contentsChildren={contentsChildren}
openDocumentIds={openDocumentIds}
writeEnabled={writeEnabled}
/>
{ this.renderDialogLayers() }
{ this.renderDocumentGrid() }
{ this.renderSnackbar() }
</div>
);
}
Expand All @@ -236,9 +276,13 @@ const mapStateToProps = state => ({
deleteDialogTitle: state.documentGrid.deleteDialogTitle,
deleteDialogBody: state.documentGrid.deleteDialogBody,
deleteDialogSubmit: state.documentGrid.deleteDialogSubmit,
snackBarOpen: state.documentGrid.snackBarOpen,
snackBarMessage: state.documentGrid.snackBarMessage,
currentLayout: layoutOptions[state.documentGrid.currentLayout],
selectedTargets: state.annotationViewer.selectedTargets,
sidebarTarget: state.annotationViewer.sidebarTarget
sidebarTarget: state.annotationViewer.sidebarTarget,
highlightSelectModes: state.textEditor.highlightSelectModes,
selectedHighlights: state.textEditor.selectedHighlights
});

const mapDispatchToProps = dispatch => bindActionCreators({
Expand All @@ -251,7 +295,10 @@ const mapDispatchToProps = dispatch => bindActionCreators({
closeDeleteDialog,
confirmDeleteDialog,
showSettings,
hideSettings
checkInAll,
updateSnackBar,
hideSettings,
selectHighlight
}, dispatch);

export default connect(
Expand Down
2 changes: 2 additions & 0 deletions client/src/TableOfContents.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import LinkableList from './LinkableList';
import ArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
import { Toolbar, ToolbarGroup, FlatButton, Drawer, IconButton } from 'material-ui';
import Settings from 'material-ui/svg-icons/action/settings';
import MoveToInbox from 'material-ui/svg-icons/content/move-to-inbox';
import { clearSelection } from './modules/annotationViewer'

class TableOfContents extends Component {
Expand Down Expand Up @@ -47,6 +48,7 @@ class TableOfContents extends Component {
icon={<CreateNewFolder />}
onClick={() => {this.props.createFolder(projectId, 'Project');}}
/>
<IconButton onClick={this.props.checkInAllClick} style={{ width: '44px', height: '44px', marginLeft: '6px' }} iconStyle={{ width: '20px', height: '20px' }}><MoveToInbox /></IconButton>
{ showSettings &&
<IconButton onClick={this.props.settingsClick} style={{ width: '44px', height: '44px', marginLeft: '6px' }} iconStyle={{ width: '20px', height: '20px' }}><Settings /></IconButton>
}
Expand Down
28 changes: 24 additions & 4 deletions client/src/TextCommands.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { chainCommands } from 'prosemirror-commands';

function markApplies(doc, ranges, type) {
for (let i = 0; i < ranges.length; i++) {
let {$from, $to} = ranges[i]
Expand Down Expand Up @@ -35,12 +37,30 @@ export function addMark(markType, attrs) {
}
}

export function removeMark(markType) {
export function removeMark(markType, uid) {
return function(state, dispatch) {
let {empty, $cursor, ranges} = state.selection
if ((empty && !$cursor) || !markApplies(state.doc, ranges, markType)) return false
if (!uid && ((empty && !$cursor) || !markApplies(state.doc, ranges, markType))) return false
if (dispatch) {
if ($cursor) {
if (markType && uid) {
let transform = null;
state.doc.descendants((node, position) => {
node.marks.forEach(mark => {
if (mark.type.name === markType.name && mark.attrs.highlightUid === uid) {
if (transform) {
// chain removal steps for additional marks onto the first step in the transform
transform.removeMark(position, position + node.nodeSize, mark);
}
else {
transform = state.tr.removeMark(position, position + node.nodeSize, mark);
}
}
});
});
if (transform) dispatch(transform);
}
// no longer used for deleting highlights
else if ($cursor) {
dispatch(state.tr.removeStoredMark(markType))
} else {
let has = false, tr = state.tr
Expand All @@ -57,4 +77,4 @@ export function removeMark(markType) {
}
return true
}
}
}
Loading

0 comments on commit b0e7253

Please sign in to comment.