UNPKG

@atlaskit/renderer

Version:
501 lines (494 loc) • 22.6 kB
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); };