285 lines
8.8 KiB
TypeScript
285 lines
8.8 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
import {GrAnnotationActionsContext} from './gr-annotation-actions-context';
|
|
import {GrDiffLine} from '../../diff/gr-diff/gr-diff-line';
|
|
import {
|
|
CoverageRange,
|
|
DiffLayer,
|
|
DiffLayerListener,
|
|
} from '../../../types/types';
|
|
import {Side} from '../../../constants/constants';
|
|
import {PluginApi} from '../../plugins/gr-plugin-types';
|
|
import {ChangeInfo, NumericChangeId} from '../../../types/common';
|
|
import {appContext} from '../../../services/app-context';
|
|
|
|
type AddLayerFunc = (ctx: GrAnnotationActionsContext) => void;
|
|
|
|
type NotifyFunc = (
|
|
path: string,
|
|
start: number,
|
|
end: number,
|
|
side: Side
|
|
) => void;
|
|
|
|
export type CoverageProvider = (
|
|
changeNum: NumericChangeId,
|
|
path: string,
|
|
basePatchNum?: number,
|
|
patchNum?: number,
|
|
change?: ChangeInfo
|
|
) => Promise<Array<CoverageRange>>;
|
|
|
|
export class GrAnnotationActionsInterface {
|
|
// Collect all annotation layers instantiated by getLayer. Will be used when
|
|
// notifying their listeners in the notify function.
|
|
private annotationLayers: AnnotationLayer[] = [];
|
|
|
|
private coverageProvider: CoverageProvider | null = null;
|
|
|
|
// Default impl is a no-op.
|
|
private addLayerFunc: AddLayerFunc = () => {};
|
|
|
|
reporting = appContext.reportingService;
|
|
|
|
constructor(private readonly plugin: PluginApi) {
|
|
// Return this instance when there is an annotatediff event.
|
|
plugin.on('annotatediff', this);
|
|
}
|
|
|
|
/**
|
|
* Register a function to call to apply annotations. Plugins should use
|
|
* GrAnnotationActionsContext.annotateRange and
|
|
* GrAnnotationActionsContext.annotateLineNumber to apply a CSS class to the
|
|
* line content or the line number.
|
|
*
|
|
* @param addLayerFunc The function
|
|
* that will be called when the AnnotationLayer is ready to annotate.
|
|
*/
|
|
addLayer(addLayerFunc: AddLayerFunc) {
|
|
this.addLayerFunc = addLayerFunc;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* The specified function will be called with a notify function for the plugin
|
|
* to call when it has all required data for annotation. Optional.
|
|
*
|
|
* @param notifyFunc See doc of the notify function below to see what it does.
|
|
*/
|
|
addNotifier(notifyFunc: (n: NotifyFunc) => void) {
|
|
notifyFunc(
|
|
(path: string, startRange: number, endRange: number, side: Side) =>
|
|
this.notify(path, startRange, endRange, side)
|
|
);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* The specified function will be called when a gr-diff component is built,
|
|
* and feeds the returned coverage data into the diff. Optional.
|
|
*
|
|
* Be sure to call this only once and only from one plugin. Multiple coverage
|
|
* providers are not supported. A second call will just overwrite the
|
|
* provider of the first call.
|
|
*/
|
|
setCoverageProvider(
|
|
coverageProvider: CoverageProvider
|
|
): GrAnnotationActionsInterface {
|
|
if (this.coverageProvider) {
|
|
console.warn('Overwriting an existing coverage provider.');
|
|
}
|
|
this.coverageProvider = coverageProvider;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Used by Gerrit to look up the coverage provider. Not intended to be called
|
|
* by plugins.
|
|
*/
|
|
getCoverageProvider() {
|
|
return this.coverageProvider;
|
|
}
|
|
|
|
/**
|
|
* Returns a checkbox HTMLElement that can be used to toggle annotations
|
|
* on/off. The checkbox will be initially disabled. Plugins should enable it
|
|
* when data is ready and should add a click handler to toggle CSS on/off.
|
|
*
|
|
* Note1: Calling this method from multiple plugins will only work for the
|
|
* 1st call. It will print an error message for all subsequent calls
|
|
* and will not invoke their onAttached functions.
|
|
* Note2: This method will be deprecated and eventually removed when
|
|
* https://bugs.chromium.org/p/gerrit/issues/detail?id=8077 is
|
|
* implemented.
|
|
*
|
|
* @param checkboxLabel Will be used as the label for the checkbox.
|
|
* Optional. "Enable" is used if this is not specified.
|
|
* @param onAttached The function that will be called
|
|
* when the checkbox is attached to the page.
|
|
*/
|
|
enableToggleCheckbox(
|
|
checkboxLabel: string,
|
|
onAttached: (checkboxEl: Element | null) => void
|
|
) {
|
|
this.plugin.hook('annotation-toggler').onAttached(element => {
|
|
if (!element.content) {
|
|
this.reporting.error(new Error('plugin endpoint without content.'));
|
|
return;
|
|
}
|
|
if (!element.content.hidden) {
|
|
this.reporting.error(
|
|
new Error(
|
|
`${element.content.id} is already enabled. Cannot re-enable.`
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
element.content.removeAttribute('hidden');
|
|
|
|
const label = element.content.querySelector('#annotation-label');
|
|
if (label) {
|
|
if (checkboxLabel) {
|
|
label.textContent = checkboxLabel;
|
|
} else {
|
|
label.textContent = 'Enable';
|
|
}
|
|
}
|
|
const checkbox = element.content.querySelector('#annotation-checkbox');
|
|
onAttached(checkbox);
|
|
});
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* The notify function will call the listeners of all required annotation
|
|
* layers. Intended to be called by the plugin when all required data for
|
|
* annotation is available.
|
|
*
|
|
* @param path The file path whose listeners should be notified.
|
|
* @param start The line where the update starts.
|
|
* @param end The line where the update ends.
|
|
* @param side The side of the update ('left' or 'right').
|
|
*/
|
|
notify(path: string, start: number, end: number, side: Side) {
|
|
for (const annotationLayer of this.annotationLayers) {
|
|
// Notify only the annotation layer that is associated with the specified
|
|
// path.
|
|
if (annotationLayer.path === path) {
|
|
annotationLayer.notifyListeners(start, end, side);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Should be called to register annotation layers by the framework. Not
|
|
* intended to be called by plugins.
|
|
*
|
|
* Don't forget to dispose layer.
|
|
*
|
|
* @param path The file path (eg: /COMMIT_MSG').
|
|
* @param changeNum The Gerrit change number.
|
|
*/
|
|
getLayer(path: string, changeNum: number) {
|
|
const annotationLayer = new AnnotationLayer(
|
|
path,
|
|
changeNum,
|
|
this.addLayerFunc
|
|
);
|
|
this.annotationLayers.push(annotationLayer);
|
|
return annotationLayer;
|
|
}
|
|
|
|
disposeLayer(path: string) {
|
|
this.annotationLayers = this.annotationLayers.filter(
|
|
annotationLayer => annotationLayer.path !== path
|
|
);
|
|
}
|
|
}
|
|
|
|
export class AnnotationLayer implements DiffLayer {
|
|
private listeners: DiffLayerListener[] = [];
|
|
|
|
/**
|
|
* Used to create an instance of the Annotation Layer interface.
|
|
*
|
|
* @param path The file path (eg: /COMMIT_MSG').
|
|
* @param changeNum The Gerrit change number.
|
|
* @param addLayerFunc The function
|
|
* that will be called when the AnnotationLayer is ready to annotate.
|
|
*/
|
|
constructor(
|
|
readonly path: string,
|
|
private readonly changeNum: number,
|
|
private readonly addLayerFunc: AddLayerFunc
|
|
) {
|
|
this.listeners = [];
|
|
}
|
|
|
|
/**
|
|
* Register a listener for layer updates.
|
|
* Don't forget to removeListener when you stop using layer.
|
|
*
|
|
* @param fn The update handler function.
|
|
* Should accept as arguments the line numbers for the start and end of
|
|
* the update and the side as a string.
|
|
*/
|
|
addListener(listener: DiffLayerListener) {
|
|
this.listeners.push(listener);
|
|
}
|
|
|
|
removeListener(listener: DiffLayerListener) {
|
|
this.listeners = this.listeners.filter(f => f !== listener);
|
|
}
|
|
|
|
/**
|
|
* Layer method to add annotations to a line.
|
|
*
|
|
* @param contentEl The DIV.contentText element of the line
|
|
* content to apply the annotation to using annotateRange.
|
|
* @param lineNumberEl The TD element of the line number to
|
|
* apply the annotation to using annotateLineNumber.
|
|
* @param line The line object.
|
|
*/
|
|
annotate(
|
|
contentEl: HTMLElement,
|
|
lineNumberEl: HTMLElement,
|
|
line: GrDiffLine
|
|
) {
|
|
const annotationActionsContext = new GrAnnotationActionsContext(
|
|
contentEl,
|
|
lineNumberEl,
|
|
line,
|
|
this.path,
|
|
this.changeNum
|
|
);
|
|
this.addLayerFunc(annotationActionsContext);
|
|
}
|
|
|
|
/**
|
|
* Notify Layer listeners of changes to annotations.
|
|
*
|
|
* @param start The line where the update starts.
|
|
* @param end The line where the update ends.
|
|
* @param side The side of the update. ('left' or 'right')
|
|
*/
|
|
notifyListeners(start: number, end: number, side: Side) {
|
|
for (const listener of this.listeners) {
|
|
listener(start, end, side);
|
|
}
|
|
}
|
|
}
|