#431 - Performance improvements for first load

This commit is contained in:
Elio Struyf
2022-10-01 20:30:18 +02:00
parent afca99b53a
commit 5fbb05f083
10 changed files with 263 additions and 190 deletions
-2
View File
@@ -68,11 +68,9 @@ describe("Initialization testing", function() {
async function notificationExists(workbench: Workbench, text: string): Promise<Notification | undefined> {
const notifications = await (await (new StatusBar()).openNotificationsCenter()).getNotifications(NotificationType.Info);
console.log(`Notifications:`, notifications.length);
for (const notification of notifications) {
const message = await notification.getMessage();
console.log(message)
if (message.indexOf(text) >= 0) {
return notification;
}
+1 -1
View File
@@ -131,7 +131,7 @@ export class Dashboard {
});
SettingsHelper.onConfigChange(() => {
SettingsListener.getSettings();
SettingsListener.getSettings(true);
});
Dashboard.webview.webview.onDidReceiveMessage(async (msg) => {
+1 -1
View File
@@ -137,7 +137,7 @@ export class Folders {
Telemetry.send(TelemetryEvent.registerFolder);
SettingsListener.getSettings();
SettingsListener.getSettings(true);
}
}
+1 -1
View File
@@ -55,7 +55,7 @@ categories: []
SettingsListener.setFramework(framework.name);
}
SettingsListener.getSettings();
SettingsListener.getSettings(true);
} catch (err: any) {
Logger.error(`Project::init: ${err?.message || err}`);
Notifications.error(`Sorry, something went wrong - ${err?.message || err}`);
+6 -1
View File
@@ -15,7 +15,7 @@ import { TagType } from './panelWebView/TagType';
import { ExplorerView } from './explorerView/ExplorerView';
import { Extension } from './helpers/Extension';
import { DashboardData } from './models/DashboardData';
import { debounceCallback, Logger, Settings as SettingsHelper } from './helpers';
import { DashboardSettings, debounceCallback, Logger, Settings as SettingsHelper } from './helpers';
import { Content } from './commands/Content';
import ContentProvider from './providers/ContentProvider';
import { Wysiwyg } from './commands/Wysiwyg';
@@ -25,6 +25,7 @@ import { Backers } from './commands/Backers';
import { DataListener, SettingsListener } from './listeners/panel';
import { NavigationType } from './dashboardWebView/models';
import { ModeSwitch } from './services/ModeSwitch';
import { PagesParser } from './services/PagesParser';
let frontMatterStatusBar: vscode.StatusBarItem;
let statusDebouncer: { (fnc: any, time: number): void; };
@@ -266,6 +267,10 @@ export async function activate(context: vscode.ExtensionContext) {
// Git
GitListener.init();
// Once everything is registered, the page parsing can start in the background
DashboardSettings.get();
PagesParser.start();
// Subscribe all commands
subscriptions.push(
insertTags,
+14 -3
View File
@@ -16,15 +16,24 @@ import { parseWinPath } from './parseWinPath';
export class DashboardSettings {
private static cachedSettings: ISettings | undefined = undefined;
public static async get() {
public static async get(clear: boolean = false) {
if (!this.cachedSettings || clear) {
this.cachedSettings = await this.getSettings();
}
return this.cachedSettings;
}
public static async getSettings() {
const ext = Extension.getInstance();
const wsFolder = Folders.getWorkspaceFolder();
const isInitialized = Project.isInitialized();
const gitActions = Settings.get<boolean>(SETTING_GIT_ENABLED);
const pagination = Settings.get<boolean>(SETTING_DASHBOARD_CONTENT_PAGINATION)
return {
const settings = {
git: {
isGitRepo: gitActions ? await GitListener.isGitRepository() : false,
actions: gitActions || false
@@ -71,7 +80,9 @@ export class DashboardSettings {
dataTypes: Settings.get<DataType[]>(SETTING_DATA_TYPES),
snippets: Settings.get<Snippets>(SETTING_CONTENT_SNIPPETS),
isBacker: await ext.getState<boolean | undefined>(CONTEXT.backer, 'global')
} as ISettings
} as ISettings;
return settings;
}
/**
+15 -175
View File
@@ -1,21 +1,17 @@
import { DEFAULT_CONTENT_TYPE_NAME } from './../../constants/ContentType';
import { isValidFile } from '../../helpers/isValidFile';
import { existsSync, unlinkSync } from "fs";
import { basename, dirname, join } from "path";
import { unlinkSync } from "fs";
import { basename } from "path";
import { commands, FileSystemWatcher, RelativePattern, TextDocument, Uri, workspace } from "vscode";
import { Dashboard } from "../../commands/Dashboard";
import { Folders } from "../../commands/Folders";
import { COMMAND_NAME, DefaultFields, ExtensionState, SETTING_SEO_DESCRIPTION_FIELD } from "../../constants";
import { COMMAND_NAME, ExtensionState } from "../../constants";
import { DashboardCommand } from "../../dashboardWebView/DashboardCommand";
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
import { Page } from "../../dashboardWebView/models";
import { ArticleHelper, Extension, Logger, Settings } from "../../helpers";
import { ContentType } from "../../helpers/ContentType";
import { DateHelper } from "../../helpers/DateHelper";
import { Notifications } from "../../helpers/Notifications";
import { ArticleHelper, Extension, Logger } from "../../helpers";
import { BaseListener } from "./BaseListener";
import { DataListener } from '../panel';
import Fuse from 'fuse.js';
import { PagesParser } from '../../services/PagesParser';
export class PagesListener extends BaseListener {
@@ -132,7 +128,7 @@ export class PagesListener extends BaseListener {
if (pageIdx !== -1) {
const stats = await workspace.fs.stat(file);
const crntPage = this.lastPages[pageIdx];
const updatedPage = this.processPageContent(file.fsPath, stats.mtime, basename(file.fsPath), crntPage.fmFolder);
const updatedPage = PagesParser.processPageContent(file.fsPath, stats.mtime, basename(file.fsPath), crntPage.fmFolder);
if (updatedPage) {
this.lastPages[pageIdx] = updatedPage;
this.sendPageData(this.lastPages);
@@ -156,43 +152,19 @@ export class PagesListener extends BaseListener {
if (cachedPages) {
this.sendPageData(cachedPages);
}
} else {
PagesParser.reset();
}
// Update the dashboard with the fresh data
const folderInfo = await Folders.getInfo();
const pages: Page[] = [];
PagesParser.getPages(async (pages: Page[]) => {
this.lastPages = pages;
this.sendPageData(pages);
if (folderInfo) {
for (const folder of folderInfo) {
for (const file of folder.lastModified) {
if (isValidFile(file.fileName)) {
try {
const page = this.processPageContent(file.filePath, file.mtime, file.fileName, folder.title);
this.sendMsg(DashboardCommand.searchReady, true);
if (page && !pages.find(p => p.fmFilePath === page.fmFilePath)) {
pages.push(page);
}
} catch (error: any) {
if ((error as Error)?.message.toLowerCase() === "webview is disposed") {
continue;
}
Logger.error(`PagesListener::getPagesData: ${file.filePath} - ${error.message}`);
Notifications.error(`File error: ${file.filePath} - ${error?.message || error}`);
}
}
}
}
}
this.lastPages = pages;
this.sendPageData(pages);
this.sendMsg(DashboardCommand.searchReady, true);
await ext.setState(ExtensionState.Dashboard.Pages.Cache, pages, "workspace");
await this.createSearchIndex(pages);
await ext.setState(ExtensionState.Dashboard.Pages.Cache, pages, "workspace");
await this.createSearchIndex(pages);
});
}
/**
@@ -245,136 +217,4 @@ export class PagesListener extends BaseListener {
public static refresh() {
this.getPagesData(true);
}
/**
* Process the page content
* @param filePath
* @param fileMtime
* @param fileName
* @param folderTitle
* @returns
*/
private static processPageContent(filePath: string, fileMtime: number, fileName: string, folderTitle: string): Page | undefined {
const article = ArticleHelper.getFrontMatterByPath(filePath);
if (article?.data.title) {
const wsFolder = Folders.getWorkspaceFolder();
const descriptionField = Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description;
const dateField = ArticleHelper.getPublishDateField(article) || DefaultFields.PublishingDate;
const dateFieldValue = article?.data[dateField] ? DateHelper.tryParse(article?.data[dateField]) : undefined;
const modifiedField = ArticleHelper.getModifiedDateField(article) || null;
const modifiedFieldValue = modifiedField && article?.data[modifiedField] ? DateHelper.tryParse(article?.data[modifiedField])?.getTime() : undefined;
const staticFolder = Folders.getStaticFolderRelativePath();
const page: Page = {
...article.data,
// FrontMatter properties
fmFolder: folderTitle,
fmFilePath: filePath,
fmFileName: fileName,
fmDraft: ContentType.getDraftStatus(article?.data),
fmModified: modifiedFieldValue ? modifiedFieldValue : fileMtime,
fmPublished: dateFieldValue ? dateFieldValue.getTime() : null,
fmYear: dateFieldValue ? dateFieldValue.getFullYear() : null,
fmPreviewImage: "",
fmTags: [],
fmCategories: [],
fmContentType: DEFAULT_CONTENT_TYPE_NAME,
fmBody: article?.content || "",
// Make sure these are always set
title: article?.data.title,
slug: article?.data.slug,
date: article?.data[dateField] || "",
draft: article?.data.draft,
description: article?.data[descriptionField] || "",
};
const contentType = ArticleHelper.getContentType(article.data);
if (contentType) {
page.fmContentType = contentType.name;
}
let previewFieldParents = ContentType.findPreviewField(contentType.fields);
if (previewFieldParents.length === 0) {
const previewField = contentType.fields.find(field => field.type === "image" && field.name === "preview");
if (previewField) {
previewFieldParents = ["preview"];
}
}
let tagParents = ContentType.findFieldByType(contentType.fields, "tags");
const tagsValue = ContentType.getFieldValue(article.data, tagParents.length !== 0 ? tagParents : ["tags"]);
page.fmTags = typeof tagsValue === "string" ? tagsValue.split(",") : tagsValue;
let categoryParents = ContentType.findFieldByType(contentType.fields, "categories");
const categoriesValue = ContentType.getFieldValue(article.data, categoryParents.length !== 0 ? categoryParents : ["categories"]);
page.fmCategories = typeof categoriesValue === "string" ? categoriesValue.split(",") : categoriesValue;
// Check if parent fields were retrieved, if not there was no image present
if (previewFieldParents.length > 0) {
let fieldValue = null;
let crntPageData = article?.data;
for (let i = 0; i < previewFieldParents.length; i++) {
const previewField = previewFieldParents[i];
if (i === previewFieldParents.length - 1) {
fieldValue = crntPageData[previewField];
} else {
if (!crntPageData[previewField]) {
continue;
}
crntPageData = crntPageData[previewField];
// Check for preview image in block data
if (crntPageData instanceof Array && crntPageData.length > 0) {
// Get the first field block that contains the next field data
const fieldData = crntPageData.find(item => item[previewFieldParents[i + 1]]);
if (fieldData) {
crntPageData = fieldData;
} else {
continue;
}
}
}
}
if (fieldValue && wsFolder) {
if (fieldValue && Array.isArray(fieldValue)) {
if (fieldValue.length > 0) {
fieldValue = fieldValue[0];
} else {
fieldValue = undefined;
}
}
// Revalidate as the array could have been empty
if (fieldValue) {
const staticPath = join(wsFolder.fsPath, staticFolder || "", fieldValue);
const contentFolderPath = join(dirname(filePath), fieldValue);
let previewUri = null;
if (existsSync(staticPath)) {
previewUri = Uri.file(staticPath);
} else if (existsSync(contentFolderPath)) {
previewUri = Uri.file(contentFolderPath);
}
if (previewUri) {
const preview = Dashboard.getWebview()?.asWebviewUri(previewUri);
page["fmPreviewImage"] = preview?.toString() || "";
}
}
}
}
return page;
}
return;
}
}
+4 -4
View File
@@ -42,15 +42,15 @@ export class SettingsListener extends BaseListener {
private static async update(data: { name: string, value: any }) {
if (data.name) {
await Settings.update(data.name, data.value);
this.getSettings();
this.getSettings(true);
}
}
/**
* Retrieve the settings for the dashboard
*/
public static async getSettings() {
const settings = await DashboardSettings.get();
public static async getSettings(clear: boolean = false) {
const settings = await DashboardSettings.get(clear);
this.sendMsg(DashboardCommand.settings, settings);
}
@@ -74,7 +74,7 @@ export class SettingsListener extends BaseListener {
}
}
SettingsListener.getSettings();
SettingsListener.getSettings(true);
}
private static addFolder(folder: string) {
+2 -2
View File
@@ -57,7 +57,7 @@ export class SnippetListener extends BaseListener {
snippets[title] = snippetContent;
await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
SettingsListener.getSettings();
SettingsListener.getSettings(true);
}
private static async updateSnippet(data: any) {
@@ -69,7 +69,7 @@ export class SnippetListener extends BaseListener {
}
await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
SettingsListener.getSettings();
SettingsListener.getSettings(true);
}
private static async insertSnippet(data: any) {
+219
View File
@@ -0,0 +1,219 @@
import { parseWinPath } from './../helpers/parseWinPath';
import { existsSync } from "fs";
import { dirname, join } from "path";
import { StatusBarAlignment, Uri, window } from "vscode";
import { Dashboard } from "../commands/Dashboard";
import { Folders } from "../commands/Folders";
import { DefaultFields, DEFAULT_CONTENT_TYPE_NAME, SETTING_SEO_DESCRIPTION_FIELD } from "../constants";
import { Page } from "../dashboardWebView/models";
import { ArticleHelper, ContentType, DateHelper, isValidFile, Logger, Notifications, Settings } from "../helpers";
export class PagesParser {
public static allPages: Page[] = [];
private static parser: Promise<void> | undefined;
private static initialized: boolean = false;
public static start() {
if (!this.parser) {
this.parser = this.parsePages();
}
}
public static getPages(cb: (pages: Page[]) => void) {
if (this.parser) {
this.parser.then(() => cb(PagesParser.allPages));
} else if (!PagesParser.initialized) {
this.parser = this.parsePages();
this.parser.then(() => cb(PagesParser.allPages));
} else if (PagesParser.allPages === undefined || PagesParser.allPages.length === 0) {
this.parser = this.parsePages();
this.parser.then(() => cb(PagesParser.allPages));
} else {
cb(PagesParser.allPages);
}
}
public static async reset() {
this.parser = undefined;
PagesParser.allPages = [];
}
public static async parsePages() {
// Update the dashboard with the fresh data
const folderInfo = await Folders.getInfo();
const pages: Page[] = [];
const statusBar = window.createStatusBarItem(StatusBarAlignment.Left);
if (folderInfo) {
statusBar.text = '$(sync~spin) Processing pages...';
statusBar.show();
for (const folder of folderInfo) {
for (const file of folder.lastModified) {
if (isValidFile(file.fileName)) {
try {
const page = this.processPageContent(file.filePath, file.mtime, file.fileName, folder.title);
if (page && !pages.find(p => p.fmFilePath === page.fmFilePath)) {
pages.push(page);
}
} catch (error: any) {
if ((error as Error)?.message.toLowerCase() === "webview is disposed") {
continue;
}
Logger.error(`PagesParser::parsePages: ${file.filePath} - ${error.message}`);
Notifications.error(`File error: ${file.filePath} - ${error?.message || error}`);
}
}
}
}
}
this.parser = undefined;
this.initialized = true;
PagesParser.allPages = [...pages];
statusBar.hide();
}
/**
* Process the page content
* @param filePath
* @param fileMtime
* @param fileName
* @param folderTitle
* @returns
*/
public static processPageContent(filePath: string, fileMtime: number, fileName: string, folderTitle: string): Page | undefined {
const article = ArticleHelper.getFrontMatterByPath(filePath);
if (article?.data.title) {
const wsFolder = Folders.getWorkspaceFolder();
const descriptionField = Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description;
const dateField = ArticleHelper.getPublishDateField(article) || DefaultFields.PublishingDate;
const dateFieldValue = article?.data[dateField] ? DateHelper.tryParse(article?.data[dateField]) : undefined;
const modifiedField = ArticleHelper.getModifiedDateField(article) || null;
const modifiedFieldValue = modifiedField && article?.data[modifiedField] ? DateHelper.tryParse(article?.data[modifiedField])?.getTime() : undefined;
const staticFolder = Folders.getStaticFolderRelativePath();
const page: Page = {
...article.data,
// FrontMatter properties
fmFolder: folderTitle,
fmFilePath: filePath,
fmFileName: fileName,
fmDraft: ContentType.getDraftStatus(article?.data),
fmModified: modifiedFieldValue ? modifiedFieldValue : fileMtime,
fmPublished: dateFieldValue ? dateFieldValue.getTime() : null,
fmYear: dateFieldValue ? dateFieldValue.getFullYear() : null,
fmPreviewImage: "",
fmTags: [],
fmCategories: [],
fmContentType: DEFAULT_CONTENT_TYPE_NAME,
fmBody: article?.content || "",
// Make sure these are always set
title: article?.data.title,
slug: article?.data.slug,
date: article?.data[dateField] || "",
draft: article?.data.draft,
description: article?.data[descriptionField] || "",
};
const contentType = ArticleHelper.getContentType(article.data);
if (contentType) {
page.fmContentType = contentType.name;
}
let previewFieldParents = ContentType.findPreviewField(contentType.fields);
if (previewFieldParents.length === 0) {
const previewField = contentType.fields.find(field => field.type === "image" && field.name === "preview");
if (previewField) {
previewFieldParents = ["preview"];
}
}
let tagParents = ContentType.findFieldByType(contentType.fields, "tags");
const tagsValue = ContentType.getFieldValue(article.data, tagParents.length !== 0 ? tagParents : ["tags"]);
page.fmTags = typeof tagsValue === "string" ? tagsValue.split(",") : tagsValue;
let categoryParents = ContentType.findFieldByType(contentType.fields, "categories");
const categoriesValue = ContentType.getFieldValue(article.data, categoryParents.length !== 0 ? categoryParents : ["categories"]);
page.fmCategories = typeof categoriesValue === "string" ? categoriesValue.split(",") : categoriesValue;
// Check if parent fields were retrieved, if not there was no image present
if (previewFieldParents.length > 0) {
let fieldValue = null;
let crntPageData = article?.data;
for (let i = 0; i < previewFieldParents.length; i++) {
const previewField = previewFieldParents[i];
if (i === previewFieldParents.length - 1) {
fieldValue = crntPageData[previewField];
} else {
if (!crntPageData[previewField]) {
continue;
}
crntPageData = crntPageData[previewField];
// Check for preview image in block data
if (crntPageData instanceof Array && crntPageData.length > 0) {
// Get the first field block that contains the next field data
const fieldData = crntPageData.find(item => item[previewFieldParents[i + 1]]);
if (fieldData) {
crntPageData = fieldData;
} else {
continue;
}
}
}
}
if (fieldValue && wsFolder) {
if (fieldValue && Array.isArray(fieldValue)) {
if (fieldValue.length > 0) {
fieldValue = fieldValue[0];
} else {
fieldValue = undefined;
}
}
// Revalidate as the array could have been empty
if (fieldValue) {
const staticPath = join(wsFolder.fsPath, staticFolder || "", fieldValue);
const contentFolderPath = join(dirname(filePath), fieldValue);
let previewUri = null;
if (existsSync(staticPath)) {
previewUri = Uri.file(staticPath);
} else if (existsSync(contentFolderPath)) {
previewUri = Uri.file(contentFolderPath);
}
if (previewUri) {
const previewPath = Dashboard.getWebview()?.asWebviewUri(previewUri);
let preview = previewPath?.toString();
if (!preview) {
const fileUrl = parseWinPath(previewUri.fsPath);
preview = `https://file%2B.vscode-resource.vscode-cdn.net/${fileUrl.startsWith(`/`) ? fileUrl.substr(1) : fileUrl}`;
}
page["fmPreviewImage"] = preview?.toString() || "";
}
}
}
}
return page;
}
return;
}
}