@atlaskit/renderer
Version:
Renderer component
501 lines (494 loc) • 22.6 kB
JavaScript
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import React, { createContext, useContext, useEffect, useMemo, useReducer, useRef } from 'react';
import { AnnotationMarkStates, AnnotationTypes } from '@atlaskit/adf-schema';
import { AnnotationUpdateEvent } from '@atlaskit/editor-common/types';
import { RendererContext } from '../../../ui/RendererActionsContext';
var initState = {
isDrafting: false,
draftId: undefined,
draftMarkRef: undefined,
draftActionResult: undefined,
annotations: {},
currentSelectedAnnotationId: undefined,
currentSelectedMarkRef: undefined,
currentHoveredAnnotationId: undefined
};
var AnnotationManagerStateContext = /*#__PURE__*/createContext(initState);
var AnnotationManagerDispatchContext = /*#__PURE__*/createContext({
annotationManager: undefined,
dispatch: function dispatch() {}
});
function reducer(state, action) {
switch (action.type) {
case 'reset':
{
return _objectSpread(_objectSpread({}, state), initState);
}
case 'loadAnnotation':
{
var currentIds = Object.keys(state.annotations);
var uids = Array.from(new Set(currentIds.concat(action.data.map(function (a) {
return a.id;
}))));
var updates = [];
var _loop = function _loop() {
var _state$annotations$id, _state$annotations$id2;
var id = _uids[_i];
var loadedAnnotation = action.data.find(function (a) {
return a.id === id;
});
if (!loadedAnnotation && ((_state$annotations$id = state.annotations[id]) === null || _state$annotations$id === void 0 ? void 0 : _state$annotations$id.markState) === AnnotationMarkStates.ACTIVE) {
// If the annotation is not in the loaded data, we need to remove it from the state. However,
// rather then removing it, we will set the mark state to resolved. This is to align it better with
// how the editor works.
updates.push({
id: id,
markState: AnnotationMarkStates.RESOLVED
});
return 0; // continue
}
if (!!(loadedAnnotation !== null && loadedAnnotation !== void 0 && loadedAnnotation.markState) && ((_state$annotations$id2 = state.annotations[id]) === null || _state$annotations$id2 === void 0 ? void 0 : _state$annotations$id2.markState) !== loadedAnnotation.markState) {
updates.push({
id: id,
markState: loadedAnnotation.markState
});
return 0; // continue
}
},
_ret;
for (var _i = 0, _uids = uids; _i < _uids.length; _i++) {
_ret = _loop();
if (_ret === 0) continue;
}
if (updates.length > 0) {
return _objectSpread(_objectSpread({}, state), {}, {
annotations: updates.reduce(function (nextAnnotations, update) {
return _objectSpread(_objectSpread({}, nextAnnotations), {}, _defineProperty({}, update.id, _objectSpread(_objectSpread({}, nextAnnotations[update.id]), update)));
}, state.annotations)
});
}
return state;
}
case 'updateAnnotation':
{
var current = state.annotations[action.data.id];
var _action$data = action.data,
id = _action$data.id,
selected = _action$data.selected,
hovered = _action$data.hovered,
_action$data$markStat = _action$data.markState,
markState = _action$data$markStat === void 0 ? current === null || current === void 0 ? void 0 : current.markState : _action$data$markStat;
var _updates = [];
// If the annotation is not currently in the state, we need to add it to the state.
if (!current) {
_updates.push({
id: id,
markState: markState !== null && markState !== void 0 ? markState : AnnotationMarkStates.ACTIVE
});
}
// The goal of the following is to enforce a single selection and a single hover state across all annotations.
var nextSelectedId = state.currentSelectedAnnotationId;
if (selected && nextSelectedId !== id) {
nextSelectedId = id;
}
// If the annotation is currently selected and it's being unselected, we need to remove it from the
// current selected annotation id.
if (selected === false && nextSelectedId === id) {
nextSelectedId = undefined;
}
var nextHoveredId = state.currentHoveredAnnotationId;
if (hovered && nextHoveredId !== id) {
nextHoveredId = id;
}
// If the annotation is currently hovered and it's being unhovered, we need to remove it from the
// current hovered annotation id.
if (hovered === false && nextHoveredId === id) {
nextHoveredId = undefined;
}
// If the annotations mark state is not the same as the current mark state, we need to update it.
if ((current === null || current === void 0 ? void 0 : current.markState) !== markState) {
_updates.push({
id: id,
markState: markState !== null && markState !== void 0 ? markState : AnnotationMarkStates.ACTIVE
});
// If the annotation is currently selected and it's being resolved, then we need to remove it from the
// current selected annotation id also.
if (markState === AnnotationMarkStates.RESOLVED && nextSelectedId === id) {
nextSelectedId = undefined;
}
}
if (_updates.length > 0 || nextSelectedId !== state.currentSelectedAnnotationId || nextHoveredId !== state.currentHoveredAnnotationId) {
return _objectSpread(_objectSpread({}, state), {}, {
currentSelectedAnnotationId: nextSelectedId,
currentHoveredAnnotationId: nextHoveredId,
annotations: _updates.reduce(function (nextAnnotations, update) {
return _objectSpread(_objectSpread({}, nextAnnotations), {}, _defineProperty({}, update.id, _objectSpread(_objectSpread({}, nextAnnotations[update.id]), update)));
}, state.annotations)
});
}
return state;
}
case 'resetSelectedAnnotation':
{
if (state.currentSelectedAnnotationId !== undefined) {
return _objectSpread(_objectSpread({}, state), {}, {
currentSelectedAnnotationId: undefined,
currentSelectedMarkRef: undefined
});
}
return state;
}
case 'resetHoveredAnnotation':
{
if (state.currentHoveredAnnotationId !== undefined) {
return _objectSpread(_objectSpread({}, state), {}, {
currentHoveredAnnotationId: undefined
});
}
return state;
}
case 'setDrafting':
{
if (state.isDrafting !== action.data.isDrafting || state.draftId !== action.data.draftId || state.draftActionResult !== action.data.draftActionResult) {
// XXX: When a draft is open the current selected annotation should no longer be selected. We need
// to decide what is better UX,
// 1 - do we want to deselct the selected annotation on draft open, if so then when draft is closed the s
// selected annotation will not come back
// 2 - do we want to still allow the selected annotation to be selected when draft is open, however the underlying
// mark style just shows the annotation as not selected when a draft is active. Then when a draft closes
// we can reopen the previous selected annotation.
return _objectSpread(_objectSpread({}, state), {}, {
isDrafting: action.data.isDrafting,
draftId: action.data.draftId,
draftActionResult: action.data.draftActionResult
});
}
return state;
}
case 'setDraftMarkRef':
{
if (state.draftMarkRef !== action.data.draftMarkRef) {
return _objectSpread(_objectSpread({}, state), {}, {
draftMarkRef: action.data.draftMarkRef
});
}
return state;
}
case 'setSelectedMarkRef':
{
if (state.currentSelectedMarkRef !== action.data.markRef) {
return _objectSpread(_objectSpread({}, state), {}, {
currentSelectedMarkRef: action.data.markRef
});
}
return state;
}
}
}
export var AnnotationManagerProvider = function AnnotationManagerProvider(_ref) {
var children = _ref.children,
annotationManager = _ref.annotationManager,
updateSubscriber = _ref.updateSubscriber;
var _useReducer = useReducer(reducer, initState),
_useReducer2 = _slicedToArray(_useReducer, 2),
state = _useReducer2[0],
dispatch = _useReducer2[1];
var actionContext = useContext(RendererContext);
useEffect(function () {
var getDraft = function getDraft() {
var _state$draftActionRes;
if (!state.isDrafting || !state.draftActionResult || !state.draftMarkRef || !state.draftId) {
return {
success: false,
reason: 'draft-not-started'
};
}
return {
success: true,
inlineNodeTypes: (_state$draftActionRes = state.draftActionResult.inlineNodeTypes) !== null && _state$draftActionRes !== void 0 ? _state$draftActionRes : [],
targetElement: state.draftMarkRef,
actionResult: {
step: state.draftActionResult.step,
doc: state.draftActionResult.doc,
inlineNodeTypes: state.draftActionResult.inlineNodeTypes,
targetNodeType: state.draftActionResult.targetNodeType,
originalSelection: state.draftActionResult.originalSelection,
numMatches: state.draftActionResult.numMatches,
matchIndex: state.draftActionResult.matchIndex,
pos: state.draftActionResult.pos
}
};
};
annotationManager === null || annotationManager === void 0 || annotationManager.hook('getDraft', getDraft);
return function () {
annotationManager === null || annotationManager === void 0 || annotationManager.unhook('getDraft', getDraft);
};
}, [annotationManager, state.draftId, state.isDrafting, state.draftMarkRef, state.draftActionResult]);
// We need to watch for the draft mark element to exist, so we can inform any listeners that the draft has been started
// and give them a reference to the element
useEffect(function () {
if (state.isDrafting && state.draftId && state.draftMarkRef && state.draftActionResult) {
var _state$draftActionRes2;
annotationManager === null || annotationManager === void 0 || annotationManager.emit({
name: 'draftAnnotationStarted',
data: {
inlineNodeTypes: (_state$draftActionRes2 = state.draftActionResult.inlineNodeTypes) !== null && _state$draftActionRes2 !== void 0 ? _state$draftActionRes2 : [],
targetElement: state.draftMarkRef,
actionResult: {
step: state.draftActionResult.step,
doc: state.draftActionResult.doc,
inlineNodeTypes: state.draftActionResult.inlineNodeTypes,
targetNodeType: state.draftActionResult.targetNodeType,
originalSelection: state.draftActionResult.originalSelection,
numMatches: state.draftActionResult.numMatches,
matchIndex: state.draftActionResult.matchIndex,
pos: state.draftActionResult.pos
}
}
});
}
}, [annotationManager, state.draftId, state.isDrafting, state.draftMarkRef, state.draftActionResult]);
useEffect(function () {
var setIsAnnotationSelected = function setIsAnnotationSelected(id, isSelected) {
var _state$annotations;
if (state.isDrafting) {
return {
success: false,
reason: 'draft-in-progress'
};
}
if (!((_state$annotations = state.annotations) !== null && _state$annotations !== void 0 && _state$annotations[id])) {
return {
success: false,
reason: 'id-not-valid'
};
}
// the annotation is currently not selected and is being selected
if (id !== state.currentSelectedAnnotationId && isSelected) {
dispatch({
type: 'updateAnnotation',
data: {
id: id,
selected: true
}
});
dispatch({
type: 'setSelectedMarkRef',
data: {
// eslint-disable-next-line @atlaskit/platform/no-direct-document-usage -- resolve mark node by id in the document
markRef: document.getElementById(id) || undefined
}
});
}
// the annotation is currently selected and is being unselected
else if (id === state.currentSelectedAnnotationId && !isSelected) {
dispatch({
type: 'resetSelectedAnnotation'
});
}
return {
success: true,
isSelected: isSelected
};
};
annotationManager === null || annotationManager === void 0 || annotationManager.hook('setIsAnnotationSelected', setIsAnnotationSelected);
return function () {
annotationManager === null || annotationManager === void 0 || annotationManager.unhook('setIsAnnotationSelected', setIsAnnotationSelected);
};
}, [annotationManager, state.isDrafting, state.annotations, state.currentSelectedAnnotationId, state.currentSelectedMarkRef]);
var prevSelectedAnnotationId = useRef(undefined);
useEffect(function () {
if (prevSelectedAnnotationId.current) {
annotationManager === null || annotationManager === void 0 || annotationManager.emit({
name: 'annotationSelectionChanged',
data: {
annotationId: prevSelectedAnnotationId.current,
isSelected: false,
inlineNodeTypes: []
}
});
}
prevSelectedAnnotationId.current = state.currentSelectedAnnotationId;
}, [state.currentSelectedAnnotationId, annotationManager]);
useEffect(function () {
if (state.currentSelectedAnnotationId && state.currentSelectedMarkRef && state.currentSelectedMarkRef.id === state.currentSelectedAnnotationId) {
annotationManager === null || annotationManager === void 0 || annotationManager.emit({
name: 'annotationSelectionChanged',
data: {
annotationId: state.currentSelectedAnnotationId,
isSelected: true,
inlineNodeTypes: []
}
});
}
}, [annotationManager, state.currentSelectedAnnotationId, state.currentSelectedMarkRef]);
useEffect(function () {
var setIsAnnotationHovered = function setIsAnnotationHovered(id, isHovered) {
var _state$annotations2;
if (!((_state$annotations2 = state.annotations) !== null && _state$annotations2 !== void 0 && _state$annotations2[id])) {
return {
success: false,
reason: 'id-not-valid'
};
}
dispatch({
type: 'updateAnnotation',
data: {
id: id,
hovered: isHovered
}
});
return {
success: true,
isHovered: isHovered
};
};
annotationManager === null || annotationManager === void 0 || annotationManager.hook('setIsAnnotationHovered', setIsAnnotationHovered);
return function () {
annotationManager === null || annotationManager === void 0 || annotationManager.unhook('setIsAnnotationHovered', setIsAnnotationHovered);
};
}, [annotationManager, state.annotations]);
useEffect(function () {
var clearAnnotation = function clearAnnotation(id) {
var _state$annotations3;
if (!((_state$annotations3 = state.annotations) !== null && _state$annotations3 !== void 0 && _state$annotations3[id])) {
return {
success: false,
reason: 'id-not-valid'
};
}
var result = actionContext.deleteAnnotation(id, AnnotationTypes.INLINE_COMMENT);
if (!result) {
return {
success: false,
reason: 'clear-failed'
};
}
var step = result.step,
doc = result.doc;
return {
success: true,
actionResult: {
step: step,
doc: doc
}
};
};
annotationManager === null || annotationManager === void 0 || annotationManager.hook('clearAnnotation', clearAnnotation);
return function () {
annotationManager === null || annotationManager === void 0 || annotationManager.unhook('clearAnnotation', clearAnnotation);
};
}, [annotationManager, state.annotations, actionContext]);
/**
* This is a temporary solution to ensure that the annotation manager state is in sync with the
* old updateSubscriber. The updateSubscriber will eventually be deprecated and the state will be managed
* by the annotation manager itself.
*/
useEffect(function () {
var onSetAnnotationState = function onSetAnnotationState(payload) {
if (!payload) {
return;
}
Object.values(payload).forEach(function (annotation) {
if (annotation.id && annotation.annotationType === AnnotationTypes.INLINE_COMMENT) {
var _annotation$state;
dispatch({
type: 'updateAnnotation',
data: {
id: annotation.id,
markState: (_annotation$state = annotation.state) !== null && _annotation$state !== void 0 ? _annotation$state : undefined
}
});
}
});
};
var onAnnotationSelected = function onAnnotationSelected(payload) {
dispatch({
type: 'updateAnnotation',
data: {
id: payload.annotationId,
selected: true
}
});
};
var onAnnotationHovered = function onAnnotationHovered(payload) {
dispatch({
type: 'updateAnnotation',
data: {
id: payload.annotationId,
hovered: true
}
});
};
var onAnnotationSelectedRemoved = function onAnnotationSelectedRemoved() {
dispatch({
type: 'resetSelectedAnnotation'
});
};
var onAnnotationHoveredRemoved = function onAnnotationHoveredRemoved() {
dispatch({
type: 'resetHoveredAnnotation'
});
};
var onAnnotationClick = function onAnnotationClick(_ref2) {
var annotationIds = _ref2.annotationIds,
eventTarget = _ref2.eventTarget,
_eventTargetType = _ref2.eventTargetType,
_viewMethod = _ref2.viewMethod;
dispatch({
type: 'updateAnnotation',
data: {
id: annotationIds[0],
selected: true
}
});
dispatch({
type: 'setSelectedMarkRef',
data: {
markRef: eventTarget
}
});
};
var onAnnotationDeselect = function onAnnotationDeselect() {
dispatch({
type: 'resetSelectedAnnotation'
});
};
updateSubscriber === null || updateSubscriber === void 0 || updateSubscriber.on(AnnotationUpdateEvent.SET_ANNOTATION_STATE, onSetAnnotationState);
updateSubscriber === null || updateSubscriber === void 0 || updateSubscriber.on(AnnotationUpdateEvent.SET_ANNOTATION_FOCUS, onAnnotationSelected);
updateSubscriber === null || updateSubscriber === void 0 || updateSubscriber.on(AnnotationUpdateEvent.SET_ANNOTATION_HOVERED, onAnnotationHovered);
updateSubscriber === null || updateSubscriber === void 0 || updateSubscriber.on(AnnotationUpdateEvent.REMOVE_ANNOTATION_FOCUS, onAnnotationSelectedRemoved);
updateSubscriber === null || updateSubscriber === void 0 || updateSubscriber.on(AnnotationUpdateEvent.REMOVE_ANNOTATION_HOVERED, onAnnotationHoveredRemoved);
updateSubscriber === null || updateSubscriber === void 0 || updateSubscriber.on(AnnotationUpdateEvent.ON_ANNOTATION_CLICK, onAnnotationClick);
updateSubscriber === null || updateSubscriber === void 0 || updateSubscriber.on(AnnotationUpdateEvent.DESELECT_ANNOTATIONS, onAnnotationDeselect);
return function () {
updateSubscriber === null || updateSubscriber === void 0 || updateSubscriber.off(AnnotationUpdateEvent.SET_ANNOTATION_STATE, onSetAnnotationState);
updateSubscriber === null || updateSubscriber === void 0 || updateSubscriber.off(AnnotationUpdateEvent.SET_ANNOTATION_FOCUS, onAnnotationSelected);
updateSubscriber === null || updateSubscriber === void 0 || updateSubscriber.off(AnnotationUpdateEvent.SET_ANNOTATION_HOVERED, onAnnotationHovered);
updateSubscriber === null || updateSubscriber === void 0 || updateSubscriber.off(AnnotationUpdateEvent.REMOVE_ANNOTATION_FOCUS, onAnnotationSelectedRemoved);
updateSubscriber === null || updateSubscriber === void 0 || updateSubscriber.off(AnnotationUpdateEvent.REMOVE_ANNOTATION_HOVERED, onAnnotationHoveredRemoved);
updateSubscriber === null || updateSubscriber === void 0 || updateSubscriber.off(AnnotationUpdateEvent.ON_ANNOTATION_CLICK, onAnnotationClick);
updateSubscriber === null || updateSubscriber === void 0 || updateSubscriber.off(AnnotationUpdateEvent.DESELECT_ANNOTATIONS, onAnnotationDeselect);
};
}, [updateSubscriber]);
var dispatchData = useMemo(function () {
return {
annotationManager: annotationManager,
dispatch: dispatch
};
}, [annotationManager, dispatch]);
return /*#__PURE__*/React.createElement(AnnotationManagerStateContext.Provider, {
value: state
}, /*#__PURE__*/React.createElement(AnnotationManagerDispatchContext.Provider, {
value: dispatchData
}, children));
};
export var useAnnotationManagerState = function useAnnotationManagerState() {
return useContext(AnnotationManagerStateContext);
};
export var useAnnotationManagerDispatch = function useAnnotationManagerDispatch() {
return useContext(AnnotationManagerDispatchContext);
};