Files
vscode-front-matter/src/helpers/MediaHelpers.ts
T
2022-02-14 15:45:16 -08:00

359 lines
12 KiB
TypeScript

import { decodeBase64Image, Extension, MediaLibrary, Notifications, parseWinPath, Settings, Sorting } from ".";
import { Dashboard } from "../commands/Dashboard";
import { Folders } from "../commands/Folders";
import { ExtensionState, HOME_PAGE_NAVIGATION_ID, SETTINGS_CONTENT_STATIC_FOLDER } from "../constants";
import { SortingOption } from "../dashboardWebView/models";
import { MediaInfo, MediaPaths, SortOrder, SortType } from "../models";
import { basename, extname, join, parse, dirname } from "path";
import { existsSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
import { commands, Uri, workspace, window, Position } from "vscode";
import imageSize from "image-size";
import { EditorHelper } from "@estruyf/vscode";
import { ExplorerView } from "../explorerView/ExplorerView";
import { SortOption } from "../dashboardWebView/constants/SortOption";
import { DataListener, MediaListener } from "../listeners/panel";
export class MediaHelpers {
private static media: MediaInfo[] = [];
/**
* Retrieve all media files
* @param page
* @param requestedFolder
* @param sort
* @returns
*/
public static async getMedia(page: number = 0, requestedFolder: string = '', sort: SortingOption | null = null) {
const wsFolder = Folders.getWorkspaceFolder();
const staticFolder = Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
const contentFolders = Folders.get();
const viewData = Dashboard.viewData;
let selectedFolder = requestedFolder;
const ext = Extension.getInstance();
const crntSort = sort === null ? await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Media.Sorting, "workspace") : sort;
// If the static folder is not set, retreive the last opened location
if (!selectedFolder) {
const stateValue = await ext.getState<string | undefined>(ExtensionState.SelectedFolder, "workspace");
if (stateValue !== HOME_PAGE_NAVIGATION_ID) {
// Support for page bundles
if (viewData?.data?.filePath && (viewData?.data?.filePath.endsWith('index.md') || viewData?.data?.filePath.endsWith('index.mdx'))) {
const folderPath = parse(viewData.data.filePath).dir;
selectedFolder = folderPath;
} else if (stateValue && existsSync(stateValue)) {
selectedFolder = stateValue;
}
}
}
// Go to the home folder
if (selectedFolder === HOME_PAGE_NAVIGATION_ID) {
selectedFolder = '';
}
let relSelectedFolderPath = selectedFolder;
const parsedPath = parseWinPath(wsFolder?.fsPath || "");
if (selectedFolder && selectedFolder.startsWith(parsedPath)) {
relSelectedFolderPath = selectedFolder.replace(parsedPath, '');
}
if (relSelectedFolderPath.startsWith('/')) {
relSelectedFolderPath = relSelectedFolderPath.substring(1);
}
let allMedia: MediaInfo[] = [];
if (relSelectedFolderPath) {
const files = await workspace.findFiles(join(relSelectedFolderPath, '/*'));
const media = await MediaHelpers.updateMediaData(MediaHelpers.filterMedia(files));
allMedia = [...media];
} else {
if (staticFolder) {
const folderSearch = join(staticFolder || "", '/*');
const files = await workspace.findFiles(folderSearch);
const media = await MediaHelpers.updateMediaData(MediaHelpers.filterMedia(files));
allMedia = [...media];
}
if (contentFolders && wsFolder) {
for (let i = 0; i < contentFolders.length; i++) {
const contentFolder = contentFolders[i];
const relFolderPath = contentFolder.path.substring(wsFolder.fsPath.length + 1);
const folderSearch = relSelectedFolderPath ? join(relSelectedFolderPath, '/*') : join(relFolderPath, '/*');
const files = await workspace.findFiles(folderSearch);
const media = await MediaHelpers.updateMediaData(MediaHelpers.filterMedia(files));
allMedia = [...allMedia, ...media];
}
}
}
MediaHelpers.media = Object.assign([], allMedia);
let files: MediaInfo[] = MediaHelpers.media;
// Retrieve the total after filtering and before the slicing happens
const total = files.length;
// Get media set
files = files.map((file) => {
try {
const metadata = MediaLibrary.getInstance().get(file.fsPath);
return {
...file,
dimensions: imageSize(file.fsPath),
...metadata
};
} catch (e) {
return {...file};
}
});
files = files.filter(f => f.mtime !== undefined);
// Sort the files
if (crntSort?.type === SortType.string) {
if (crntSort.id === SortOption.AltAsc || crntSort.id === SortOption.AltDesc) {
files = files.sort(Sorting.alphabetically("alt"));
} else if (crntSort.id === SortOption.CaptionAsc || crntSort.id === SortOption.CaptionDesc) {
files = files.sort(Sorting.alphabetically("caption"));
} else {
files = files.sort(Sorting.alphabetically("fsPath"));
}
} else if (crntSort?.type === SortType.number && (crntSort?.id === SortOption.SizeAsc || crntSort?.id === SortOption.SizeDesc)) {
files = files.sort(Sorting.numerically("size"));
} else if (crntSort?.type === SortType.date) {
files = files.sort(Sorting.dateWithFallback("mtime", "fsPath"));
} else {
files = files.sort(Sorting.alphabetically("fsPath"));
}
if (crntSort?.order === SortOrder.desc) {
files = files.reverse();
}
// Retrieve all the folders
let allContentFolders: string[] = [];
let allFolders: string[] = [];
if (selectedFolder) {
if (existsSync(selectedFolder)) {
allFolders = readdirSync(selectedFolder, { withFileTypes: true }).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(selectedFolder, dir.name)));
}
} else {
for (const contentFolder of contentFolders) {
const contentPath = contentFolder.path;
if (contentPath && existsSync(contentPath)) {
const subFolders = readdirSync(contentPath, { withFileTypes: true }).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(contentPath, dir.name)));
allContentFolders = [...allContentFolders, ...subFolders];
}
}
const staticPath = join(parseWinPath(wsFolder?.fsPath || ""), staticFolder || "");
if (staticPath && existsSync(staticPath)) {
allFolders = readdirSync(staticPath, { withFileTypes: true }).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(staticPath, dir.name)));
}
}
// Store the last opened folder
await Extension.getInstance().setState(ExtensionState.SelectedFolder, requestedFolder === HOME_PAGE_NAVIGATION_ID ? HOME_PAGE_NAVIGATION_ID : selectedFolder, "workspace");
let sortedFolders = [...allContentFolders, ...allFolders];
sortedFolders = sortedFolders.sort((a, b) => {
if (a.toLowerCase() < b.toLowerCase()) {
return -1;
}
if (a.toLowerCase() > b.toLowerCase()) {
return 1;
}
return 0;
});
if (crntSort?.order === SortOrder.desc) {
sortedFolders = sortedFolders.reverse();
}
return {
media: files,
total: total,
folders: sortedFolders,
selectedFolder
} as MediaPaths
}
/**
* Reset media array
*/
public static resetMedia() {
MediaHelpers.media = [];
}
/**
* Save the dropped file in the current folder
* @param fileData
*/
public static async saveFile({fileName, contents, folder}: { fileName: string; contents: string; folder: string | null }) {
if (fileName && contents) {
const wsFolder = Folders.getWorkspaceFolder();
const staticFolder = Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
const wsPath = wsFolder ? wsFolder.fsPath : "";
let absFolderPath = join(wsPath, staticFolder || "");
if (folder) {
absFolderPath = folder;
}
if (!existsSync(absFolderPath)) {
absFolderPath = join(wsPath, folder || "");
}
if (!existsSync(absFolderPath)) {
Notifications.error(`We couldn't find your selected folder.`);
return;
}
const staticPath = join(absFolderPath, fileName);
const imgData = decodeBase64Image(contents);
if (imgData) {
writeFileSync(staticPath, imgData.data);
Notifications.info(`File ${fileName} uploaded to: ${folder}`);
return true;
} else {
Notifications.error(`Something went wrong uploading ${fileName}`);
throw new Error(`Something went wrong uploading ${fileName}`);
}
}
return false;
}
/**
* Delete the selected file
* @param data
* @returns
*/
public static async deleteFile({ file, page, folder }: { file: string; page: number; folder: string | null; }) {
if (!file) {
return;
}
try {
unlinkSync(file);
MediaHelpers.media = [];
return true;
} catch(err: any) {
Notifications.error(`Something went wrong deleting ${basename(file)}`);
throw new Error(`Something went wrong deleting ${basename(file)}`);
}
}
/**
* Insert an image into the front matter or contents
* @param data
*/
public static async insertMediaToMarkdown(data: any) {
if (data?.file && data?.image) {
if (!data?.position) {
await commands.executeCommand(`workbench.view.extension.frontmatter-explorer`);
}
await EditorHelper.showFile(data.file);
Dashboard.resetViewData();
const extensionUri = Extension.getInstance().extensionPath;
const panel = ExplorerView.getInstance(extensionUri);
if (data?.position) {
const wsFolder = Folders.getWorkspaceFolder();
const editor = window.activeTextEditor;
const line = data.position.line;
const character = data.position.character;
if (line) {
let imgPath = data.image;
const filePath = data.file;
const absImgPath = join(parseWinPath(wsFolder?.fsPath || ""), imgPath);
const imgDir = dirname(absImgPath);
const fileDir = dirname(filePath);
if (imgDir === fileDir) {
imgPath = join('/', basename(imgPath));
// Snippets are already parsed, so update the URL of the image
if (data.snippet) {
data.snippet = data.snippet.replace(data.image, imgPath);
}
}
const selection = editor?.selection;
await editor?.edit(builder => {
const snippet = data.snippet || `![${data.alt || data.caption || ""}](${imgPath})`;
if (selection !== undefined) {
builder.replace(selection, snippet);
} else {
builder.insert(new Position(line, character), snippet);
}
});
}
MediaListener.getMediaSelection();
} else {
MediaListener.getMediaSelection();
DataListener.updateMetadata({
field: data.fieldName,
value: data.image,
parents: data.parents,
blockData: data.blockData
});
}
}
}
/**
* Update the metadata of a media file
* @param data
*/
public static updateMetadata(data: any) {
const { file, filename, page, folder, ...metadata }: { file:string; filename:string; page: number; folder: string | null; metadata: any; } = data;
const mediaLib = MediaLibrary.getInstance();
mediaLib.set(file, metadata);
// Check if filename needs to be updated
mediaLib.updateFilename(file, filename);
}
/**
* Filter the media files
*/
private static filterMedia(files: Uri[]) {
return files.filter(file => {
const ext = extname(file.fsPath);
return ['.jpg', '.jpeg', '.png', '.gif', '.svg'].includes(ext.toLowerCase());
}).map((file) => ({
filename: basename(file.fsPath),
fsPath: file.fsPath,
vsPath: Dashboard.getWebview()?.asWebviewUri(file).toString(),
stats: undefined
} as MediaInfo));
}
/**
* Update the metadata of the retrieved files
* @param files
*/
private static async updateMediaData(files: MediaInfo[]) {
files = files.map((m: MediaInfo) => {
const stats = statSync(m.fsPath);
return Object.assign({}, m, stats);
});
return Object.assign([], files);
}
}