chrome-devtools-frontend
Version:
Chrome DevTools UI
297 lines (258 loc) • 10.2 kB
text/typescript
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* eslint-disable rulesdir/no_underscored_properties */
import * as Common from '../common/common.js';
import * as TextUtils from '../text_utils/text_utils.js'; // eslint-disable-line no-unused-vars
import {UISourceCode, UISourceCodeMetadata} from './UISourceCode.js'; // eslint-disable-line no-unused-vars
export interface ProjectSearchConfig {
query(): string;
ignoreCase(): boolean;
isRegex(): boolean;
queries(): string[];
filePathMatchesFileQuery(filePath: string): boolean;
}
export abstract class Project {
abstract workspace(): WorkspaceImpl;
abstract id(): string;
abstract type(): string;
abstract isServiceProject(): boolean;
abstract displayName(): string;
abstract requestMetadata(uiSourceCode: UISourceCode): Promise<UISourceCodeMetadata|null>;
abstract requestFileContent(uiSourceCode: UISourceCode): Promise<TextUtils.ContentProvider.DeferredContent>;
abstract canSetFileContent(): boolean;
abstract setFileContent(uiSourceCode: UISourceCode, newContent: string, isBase64: boolean): Promise<void>;
abstract fullDisplayName(uiSourceCode: UISourceCode): string;
abstract mimeType(uiSourceCode: UISourceCode): string;
abstract canRename(): boolean;
rename(
_uiSourceCode: UISourceCode, _newName: string,
_callback: (arg0: boolean, arg1?: string, arg2?: string, arg3?: Common.ResourceType.ResourceType) => void): void {
}
excludeFolder(_path: string): void {
}
abstract canExcludeFolder(path: string): boolean;
abstract createFile(path: string, name: string|null, content: string, isBase64?: boolean): Promise<UISourceCode|null>;
abstract canCreateFile(): boolean;
deleteFile(_uiSourceCode: UISourceCode): void {
}
remove(): void {
}
abstract searchInFileContent(uiSourceCode: UISourceCode, query: string, caseSensitive: boolean, isRegex: boolean):
Promise<TextUtils.ContentProvider.SearchMatch[]>;
abstract findFilesMatchingSearchRequest(
searchConfig: ProjectSearchConfig, filesMathingFileQuery: string[],
progress: Common.Progress.Progress): Promise<string[]>;
indexContent(_progress: Common.Progress.Progress): void {
}
abstract uiSourceCodeForURL(url: string): UISourceCode|null;
abstract uiSourceCodes(): UISourceCode[];
}
// TODO(crbug.com/1167717): Make this a const enum again
// eslint-disable-next-line rulesdir/const_enum, @typescript-eslint/naming-convention
export enum projectTypes {
Debugger = 'debugger',
Formatter = 'formatter',
Network = 'network',
FileSystem = 'filesystem',
ContentScripts = 'contentscripts',
Service = 'service',
}
export class ProjectStore {
_workspace: WorkspaceImpl;
_id: string;
_type: projectTypes;
_displayName: string;
_uiSourceCodesMap: Map<string, {
uiSourceCode: UISourceCode,
index: number,
}>;
_uiSourceCodesList: UISourceCode[];
_project: Project;
constructor(workspace: WorkspaceImpl, id: string, type: projectTypes, displayName: string) {
this._workspace = workspace;
this._id = id;
this._type = type;
this._displayName = displayName;
this._uiSourceCodesMap = new Map();
this._uiSourceCodesList = [];
this._project = (this as unknown as Project);
}
id(): string {
return this._id;
}
type(): string {
return this._type;
}
displayName(): string {
return this._displayName;
}
workspace(): WorkspaceImpl {
return this._workspace;
}
createUISourceCode(url: string, contentType: Common.ResourceType.ResourceType): UISourceCode {
return new UISourceCode(this._project, url, contentType);
}
addUISourceCode(uiSourceCode: UISourceCode): boolean {
const url = uiSourceCode.url();
if (this.uiSourceCodeForURL(url)) {
return false;
}
this._uiSourceCodesMap.set(url, {uiSourceCode: uiSourceCode, index: this._uiSourceCodesList.length});
this._uiSourceCodesList.push(uiSourceCode);
this._workspace.dispatchEventToListeners(Events.UISourceCodeAdded, uiSourceCode);
return true;
}
removeUISourceCode(url: string): void {
const uiSourceCode = this.uiSourceCodeForURL(url);
if (!uiSourceCode) {
return;
}
const entry = this._uiSourceCodesMap.get(url);
if (!entry) {
return;
}
const movedUISourceCode = this._uiSourceCodesList[this._uiSourceCodesList.length - 1];
this._uiSourceCodesList[entry.index] = movedUISourceCode;
const movedEntry = this._uiSourceCodesMap.get(movedUISourceCode.url());
if (movedEntry) {
movedEntry.index = entry.index;
}
this._uiSourceCodesList.splice(this._uiSourceCodesList.length - 1, 1);
this._uiSourceCodesMap.delete(url);
this._workspace.dispatchEventToListeners(Events.UISourceCodeRemoved, entry.uiSourceCode);
}
removeProject(): void {
this._workspace._removeProject(this._project);
this._uiSourceCodesMap = new Map();
this._uiSourceCodesList = [];
}
uiSourceCodeForURL(url: string): UISourceCode|null {
const entry = this._uiSourceCodesMap.get(url);
return entry ? entry.uiSourceCode : null;
}
uiSourceCodes(): UISourceCode[] {
return this._uiSourceCodesList;
}
renameUISourceCode(uiSourceCode: UISourceCode, newName: string): void {
const oldPath = uiSourceCode.url();
const newPath = uiSourceCode.parentURL() ? uiSourceCode.parentURL() + '/' + newName : newName;
const value = this._uiSourceCodesMap.get(oldPath) as {
uiSourceCode: UISourceCode,
index: number,
};
this._uiSourceCodesMap.set(newPath, value);
this._uiSourceCodesMap.delete(oldPath);
}
}
let workspaceInstance: WorkspaceImpl;
export class WorkspaceImpl extends Common.ObjectWrapper.ObjectWrapper {
_projects: Map<string, Project>;
_hasResourceContentTrackingExtensions: boolean;
private constructor() {
super();
this._projects = new Map();
this._hasResourceContentTrackingExtensions = false;
}
static instance(opts: {forceNew: boolean|null} = {forceNew: null}): WorkspaceImpl {
const {forceNew} = opts;
if (!workspaceInstance || forceNew) {
workspaceInstance = new WorkspaceImpl();
}
return workspaceInstance;
}
uiSourceCode(projectId: string, url: string): UISourceCode|null {
const project = this._projects.get(projectId);
return project ? project.uiSourceCodeForURL(url) : null;
}
uiSourceCodeForURL(url: string): UISourceCode|null {
for (const project of this._projects.values()) {
const uiSourceCode = project.uiSourceCodeForURL(url);
if (uiSourceCode) {
return uiSourceCode;
}
}
return null;
}
uiSourceCodesForProjectType(type: string): UISourceCode[] {
const result: UISourceCode[] = [];
for (const project of this._projects.values()) {
if (project.type() === type) {
result.push(...project.uiSourceCodes());
}
}
return result;
}
addProject(project: Project): void {
console.assert(!this._projects.has(project.id()), `A project with id ${project.id()} already exists!`);
this._projects.set(project.id(), project);
this.dispatchEventToListeners(Events.ProjectAdded, project);
}
_removeProject(project: Project): void {
this._projects.delete(project.id());
this.dispatchEventToListeners(Events.ProjectRemoved, project);
}
project(projectId: string): Project|null {
return this._projects.get(projectId) || null;
}
projects(): Project[] {
return [...this._projects.values()];
}
projectsForType(type: string): Project[] {
function filterByType(project: Project): boolean {
return project.type() === type;
}
return this.projects().filter(filterByType);
}
uiSourceCodes(): UISourceCode[] {
const result: UISourceCode[] = [];
for (const project of this._projects.values()) {
result.push(...project.uiSourceCodes());
}
return result;
}
setHasResourceContentTrackingExtensions(hasExtensions: boolean): void {
this._hasResourceContentTrackingExtensions = hasExtensions;
}
hasResourceContentTrackingExtensions(): boolean {
return this._hasResourceContentTrackingExtensions;
}
}
// TODO(crbug.com/1167717): Make this a const enum again
// eslint-disable-next-line rulesdir/const_enum
export enum Events {
UISourceCodeAdded = 'UISourceCodeAdded',
UISourceCodeRemoved = 'UISourceCodeRemoved',
UISourceCodeRenamed = 'UISourceCodeRenamed',
WorkingCopyChanged = 'WorkingCopyChanged',
WorkingCopyCommitted = 'WorkingCopyCommitted',
WorkingCopyCommittedByUser = 'WorkingCopyCommittedByUser',
ProjectAdded = 'ProjectAdded',
ProjectRemoved = 'ProjectRemoved',
}