2021-07-03 21:46:40 -07:00
|
|
|
import type { FSModule } from "browserfs/dist/node/core/FS";
|
2021-09-11 21:26:52 -07:00
|
|
|
import { monacoExtensions } from "components/apps/MonacoEditor/config";
|
2021-11-27 21:52:53 -08:00
|
|
|
import { isYouTubeUrl } from "components/apps/VideoPlayer/useVideoPlayer";
|
2021-08-28 22:21:00 -07:00
|
|
|
import type { ExtensionType } from "components/system/Files/FileEntry/extensions";
|
2021-07-03 21:41:57 -07:00
|
|
|
import extensions from "components/system/Files/FileEntry/extensions";
|
2021-07-03 21:46:40 -07:00
|
|
|
import type { FileInfo } from "components/system/Files/FileEntry/useFileInfo";
|
2021-11-20 23:20:32 -08:00
|
|
|
import type { FileStat } from "components/system/Files/FileManager/functions";
|
2021-07-10 22:43:05 -07:00
|
|
|
import processDirectory from "contexts/process/directory";
|
2021-07-03 21:46:40 -07:00
|
|
|
import ini from "ini";
|
2021-09-25 21:23:24 -07:00
|
|
|
import { extname, join } from "path";
|
2021-10-30 22:04:01 -07:00
|
|
|
import index from "public/.index/fs.9p.json";
|
2021-07-03 23:08:32 -07:00
|
|
|
import {
|
2021-09-25 22:59:20 -07:00
|
|
|
BASE_2D_CONTEXT_OPTIONS,
|
2021-07-31 21:34:00 -07:00
|
|
|
EMPTY_BUFFER,
|
2021-11-20 23:15:26 -08:00
|
|
|
FOLDER_ICON,
|
2021-07-03 23:08:32 -07:00
|
|
|
IMAGE_FILE_EXTENSIONS,
|
|
|
|
|
MP3_MIME_TYPE,
|
2021-11-20 23:15:26 -08:00
|
|
|
NEW_FOLDER_ICON,
|
2021-09-25 22:51:56 -07:00
|
|
|
ONE_TIME_PASSIVE_EVENT,
|
|
|
|
|
PREVIEW_FRAME_SECOND,
|
2021-07-03 23:08:32 -07:00
|
|
|
SHORTCUT_EXTENSION,
|
2021-09-25 23:18:03 -07:00
|
|
|
SHORTCUT_ICON,
|
2021-07-17 22:46:03 -07:00
|
|
|
SYSTEM_FILES,
|
2021-09-25 21:44:16 -07:00
|
|
|
SYSTEM_PATHS,
|
2021-11-20 23:15:26 -08:00
|
|
|
UNKNOWN_ICON,
|
2021-09-25 22:51:56 -07:00
|
|
|
VIDEO_FILE_EXTENSIONS,
|
2021-07-03 23:08:32 -07:00
|
|
|
} from "utils/constants";
|
2021-07-03 21:46:40 -07:00
|
|
|
import { bufferToUrl } from "utils/functions";
|
2021-04-03 21:51:11 -07:00
|
|
|
|
2021-08-21 22:31:01 -07:00
|
|
|
type InternetShortcut = {
|
|
|
|
|
InternetShortcut: {
|
|
|
|
|
BaseURL: string;
|
2021-11-06 22:26:44 -07:00
|
|
|
Comment: string;
|
2021-08-21 22:31:01 -07:00
|
|
|
IconFile: string;
|
2021-10-02 23:02:44 -07:00
|
|
|
Type: string;
|
2021-08-21 22:31:01 -07:00
|
|
|
URL: string;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2021-09-25 21:44:16 -07:00
|
|
|
type ShellClassInfo = {
|
|
|
|
|
ShellClassInfo: {
|
|
|
|
|
IconFile: string;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2021-10-30 22:04:01 -07:00
|
|
|
type FS9P = [string, number, number, number, number, number, FS9P[] | string];
|
|
|
|
|
|
|
|
|
|
const IDX_MTIME = 2;
|
|
|
|
|
const IDX_TARGET = 6;
|
|
|
|
|
|
2021-11-27 23:57:03 -08:00
|
|
|
const get9pModifiedTime = (path: string): number => {
|
2021-10-30 22:04:01 -07:00
|
|
|
let fsPath = index.fsroot as FS9P[];
|
|
|
|
|
let mTime = 0;
|
|
|
|
|
|
|
|
|
|
path
|
|
|
|
|
.split("/")
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.forEach((pathPart) => {
|
|
|
|
|
const pathBranch = fsPath.find(([name]) => name === pathPart);
|
|
|
|
|
|
|
|
|
|
if (pathBranch) {
|
|
|
|
|
const isBranch = Array.isArray(pathBranch[IDX_TARGET]);
|
|
|
|
|
|
|
|
|
|
if (!isBranch) mTime = pathBranch[IDX_MTIME];
|
|
|
|
|
fsPath = isBranch ? (pathBranch[IDX_TARGET] as FS9P[]) : [];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return mTime;
|
|
|
|
|
};
|
|
|
|
|
|
2021-11-20 23:20:32 -08:00
|
|
|
export const getModifiedTime = (path: string, stats: FileStat): number => {
|
|
|
|
|
const { atimeMs, ctimeMs, mtimeMs } = stats;
|
|
|
|
|
|
|
|
|
|
return atimeMs === ctimeMs && ctimeMs === mtimeMs
|
|
|
|
|
? get9pModifiedTime(path) || mtimeMs
|
|
|
|
|
: mtimeMs;
|
|
|
|
|
};
|
|
|
|
|
|
2021-09-25 22:27:51 -07:00
|
|
|
export const getIconFromIni = (
|
|
|
|
|
fs: FSModule,
|
|
|
|
|
directory: string
|
|
|
|
|
): Promise<string> =>
|
2021-11-20 21:37:26 -08:00
|
|
|
new Promise((resolve) => {
|
2021-09-25 22:27:51 -07:00
|
|
|
fs.readFile(
|
|
|
|
|
join(directory, "desktop.ini"),
|
|
|
|
|
(error, contents = EMPTY_BUFFER) => {
|
|
|
|
|
if (!error) {
|
|
|
|
|
const {
|
|
|
|
|
ShellClassInfo: { IconFile = "" },
|
|
|
|
|
} = ini.parse(contents.toString()) as ShellClassInfo;
|
|
|
|
|
|
|
|
|
|
if (IconFile) resolve(IconFile);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-11-20 21:37:26 -08:00
|
|
|
);
|
|
|
|
|
});
|
2021-09-25 22:27:51 -07:00
|
|
|
|
2021-09-11 23:08:40 -07:00
|
|
|
const getDefaultFileViewer = (extension: string): string => {
|
|
|
|
|
if (monacoExtensions.has(extension)) return "MonacoEditor";
|
|
|
|
|
if (IMAGE_FILE_EXTENSIONS.has(extension)) return "Photos";
|
2021-09-25 22:56:09 -07:00
|
|
|
if (VIDEO_FILE_EXTENSIONS.has(extension)) return "VideoPlayer";
|
2021-09-11 23:08:40 -07:00
|
|
|
|
|
|
|
|
return "";
|
|
|
|
|
};
|
2021-09-11 21:22:37 -07:00
|
|
|
|
2021-09-11 21:33:28 -07:00
|
|
|
export const getIconByFileExtension = (extension: string): string => {
|
2021-08-28 22:21:00 -07:00
|
|
|
const { icon: extensionIcon = "", process: [defaultProcess = ""] = [] } =
|
|
|
|
|
extension in extensions ? extensions[extension as ExtensionType] : {};
|
2021-07-10 22:43:05 -07:00
|
|
|
|
2021-09-18 21:42:41 -07:00
|
|
|
if (extensionIcon) return `/System/Icons/${extensionIcon}.png`;
|
2021-07-10 22:43:05 -07:00
|
|
|
|
2021-09-04 21:55:55 -07:00
|
|
|
return (
|
2021-09-11 21:22:37 -07:00
|
|
|
processDirectory[defaultProcess || getDefaultFileViewer(extension)]?.icon ||
|
2021-09-25 22:27:51 -07:00
|
|
|
UNKNOWN_ICON
|
2021-09-04 21:55:55 -07:00
|
|
|
);
|
2021-07-10 22:43:05 -07:00
|
|
|
};
|
2021-06-26 22:01:45 -07:00
|
|
|
|
2021-09-11 22:11:58 -07:00
|
|
|
export const getProcessByFileExtension = (extension: string): string => {
|
2021-08-28 22:21:00 -07:00
|
|
|
const [defaultProcess = ""] =
|
|
|
|
|
extension in extensions
|
|
|
|
|
? extensions[extension as ExtensionType].process
|
2021-09-11 21:22:37 -07:00
|
|
|
: [getDefaultFileViewer(extension)];
|
2021-06-26 22:01:45 -07:00
|
|
|
|
|
|
|
|
return defaultProcess;
|
2021-04-04 00:14:27 -07:00
|
|
|
};
|
2021-07-03 21:46:40 -07:00
|
|
|
|
2021-10-02 23:02:44 -07:00
|
|
|
export const getShortcutInfo = (contents: Buffer): FileInfo => {
|
2021-07-03 21:52:14 -07:00
|
|
|
const {
|
2021-10-02 23:02:44 -07:00
|
|
|
InternetShortcut: {
|
|
|
|
|
BaseURL: pid = "",
|
2021-11-06 22:26:44 -07:00
|
|
|
Comment: comment = "",
|
2021-10-02 23:02:44 -07:00
|
|
|
IconFile: icon = "",
|
|
|
|
|
Type: type = "",
|
|
|
|
|
URL: url = "",
|
|
|
|
|
},
|
2021-08-21 22:31:01 -07:00
|
|
|
} = ini.parse(contents.toString()) as InternetShortcut;
|
2021-07-03 21:52:14 -07:00
|
|
|
|
2021-09-25 21:13:00 -07:00
|
|
|
if (!icon && pid) {
|
2021-11-06 22:26:44 -07:00
|
|
|
return { comment, icon: processDirectory[pid]?.icon, pid, type, url };
|
2021-09-25 21:13:00 -07:00
|
|
|
}
|
|
|
|
|
|
2021-11-06 22:26:44 -07:00
|
|
|
return { comment, icon, pid, type, url };
|
2021-07-03 21:52:14 -07:00
|
|
|
};
|
|
|
|
|
|
2021-07-03 21:46:40 -07:00
|
|
|
export const getInfoWithoutExtension = (
|
2021-09-25 21:44:16 -07:00
|
|
|
fs: FSModule,
|
2021-07-03 21:46:40 -07:00
|
|
|
path: string,
|
2021-09-25 21:44:16 -07:00
|
|
|
isDirectory: boolean,
|
2021-11-20 22:36:11 -08:00
|
|
|
useNewFolderIcon: boolean,
|
2021-09-25 21:44:16 -07:00
|
|
|
callback: (value: FileInfo) => void
|
|
|
|
|
): void => {
|
|
|
|
|
if (isDirectory) {
|
2021-10-23 22:13:17 -07:00
|
|
|
const setFolderInfo = (icon: string, getIcon?: () => void): void =>
|
|
|
|
|
callback({ getIcon, icon, pid: "FileExplorer", url: path });
|
2021-09-25 21:44:16 -07:00
|
|
|
|
2021-11-20 22:36:11 -08:00
|
|
|
setFolderInfo(useNewFolderIcon ? NEW_FOLDER_ICON : FOLDER_ICON, () =>
|
2021-10-23 22:13:17 -07:00
|
|
|
getIconFromIni(fs, path).then(setFolderInfo)
|
|
|
|
|
);
|
2021-09-25 21:44:16 -07:00
|
|
|
} else {
|
2021-09-25 22:27:51 -07:00
|
|
|
callback({ icon: UNKNOWN_ICON, pid: "", url: "" });
|
2021-09-25 21:44:16 -07:00
|
|
|
}
|
|
|
|
|
};
|
2021-07-03 21:46:40 -07:00
|
|
|
|
|
|
|
|
export const getInfoWithExtension = (
|
|
|
|
|
fs: FSModule,
|
|
|
|
|
path: string,
|
|
|
|
|
extension: string,
|
2021-09-25 21:23:24 -07:00
|
|
|
callback: (value: FileInfo) => void
|
2021-07-03 21:46:40 -07:00
|
|
|
): void => {
|
2021-09-25 23:14:40 -07:00
|
|
|
const subIcons: string[] = [];
|
2021-10-23 22:13:17 -07:00
|
|
|
const getInfoByFileExtension = (icon?: string, getIcon?: () => void): void =>
|
2021-07-03 21:46:40 -07:00
|
|
|
callback({
|
2021-10-23 22:13:17 -07:00
|
|
|
getIcon,
|
2021-07-03 21:46:40 -07:00
|
|
|
icon: icon || getIconByFileExtension(extension),
|
|
|
|
|
pid: getProcessByFileExtension(extension),
|
2021-09-25 23:14:40 -07:00
|
|
|
subIcons,
|
2021-07-03 21:46:40 -07:00
|
|
|
url: path,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (extension === SHORTCUT_EXTENSION) {
|
2021-07-31 21:20:31 -07:00
|
|
|
fs.readFile(path, (error, contents = EMPTY_BUFFER) => {
|
2021-09-25 23:18:03 -07:00
|
|
|
subIcons.push(SHORTCUT_ICON);
|
2021-09-25 23:14:40 -07:00
|
|
|
|
2021-07-03 21:46:40 -07:00
|
|
|
if (error) {
|
|
|
|
|
getInfoByFileExtension();
|
|
|
|
|
} else {
|
2021-11-06 22:26:44 -07:00
|
|
|
const { comment, icon, pid, url } = getShortcutInfo(contents);
|
2021-09-25 21:23:24 -07:00
|
|
|
const urlExt = extname(url);
|
|
|
|
|
|
2021-10-02 22:10:35 -07:00
|
|
|
if (pid === "FileExplorer") {
|
2021-10-23 22:13:17 -07:00
|
|
|
const getIcon = (): void => {
|
|
|
|
|
getIconFromIni(fs, url).then((iniIcon) =>
|
2021-11-20 23:29:58 -08:00
|
|
|
callback({ comment, icon: iniIcon, pid, subIcons, url })
|
2021-10-23 22:13:17 -07:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2021-11-06 22:26:44 -07:00
|
|
|
callback({ comment, getIcon, icon, pid, subIcons, url });
|
2021-10-02 22:10:35 -07:00
|
|
|
} else if (
|
2021-09-25 22:56:09 -07:00
|
|
|
IMAGE_FILE_EXTENSIONS.has(urlExt) ||
|
|
|
|
|
VIDEO_FILE_EXTENSIONS.has(urlExt) ||
|
|
|
|
|
urlExt === ".mp3"
|
|
|
|
|
) {
|
2021-10-23 22:13:17 -07:00
|
|
|
getInfoWithExtension(fs, url, urlExt, (fileInfo) => {
|
2021-11-27 23:07:37 -08:00
|
|
|
const { icon: urlIcon = icon, getIcon } = fileInfo;
|
2021-10-23 22:13:17 -07:00
|
|
|
|
2021-11-27 23:07:37 -08:00
|
|
|
callback({ comment, getIcon, icon: urlIcon, pid, subIcons, url });
|
2021-09-25 21:23:24 -07:00
|
|
|
});
|
2021-11-27 21:52:53 -08:00
|
|
|
} else if (isYouTubeUrl(url)) {
|
|
|
|
|
callback({
|
|
|
|
|
comment,
|
|
|
|
|
icon: `https://img.youtube.com/vi${new URL(url).pathname}/1.jpg`,
|
|
|
|
|
pid,
|
|
|
|
|
subIcons: [processDirectory["VideoPlayer"].icon],
|
|
|
|
|
url,
|
|
|
|
|
});
|
2021-10-23 22:13:17 -07:00
|
|
|
} else {
|
2021-11-06 22:26:44 -07:00
|
|
|
callback({ comment, icon, pid, subIcons, url });
|
2021-09-25 21:23:24 -07:00
|
|
|
}
|
2021-07-03 21:46:40 -07:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else if (IMAGE_FILE_EXTENSIONS.has(extension)) {
|
2021-10-23 22:13:17 -07:00
|
|
|
getInfoByFileExtension("/System/Icons/photo.png", () =>
|
|
|
|
|
fs.readFile(path, (error, contents = EMPTY_BUFFER) => {
|
|
|
|
|
if (!error) getInfoByFileExtension(bufferToUrl(contents));
|
|
|
|
|
})
|
|
|
|
|
);
|
2021-09-25 22:51:56 -07:00
|
|
|
} else if (VIDEO_FILE_EXTENSIONS.has(extension)) {
|
2021-11-13 22:26:22 -08:00
|
|
|
getInfoByFileExtension(processDirectory["VideoPlayer"].icon, () =>
|
|
|
|
|
fs.readFile(path, (error, contents = EMPTY_BUFFER) => {
|
|
|
|
|
if (!error) {
|
|
|
|
|
const video = document.createElement("video");
|
|
|
|
|
|
|
|
|
|
video.currentTime = PREVIEW_FRAME_SECOND;
|
|
|
|
|
video.addEventListener(
|
|
|
|
|
"loadeddata",
|
|
|
|
|
() => {
|
|
|
|
|
const canvas = document.createElement("canvas");
|
|
|
|
|
|
|
|
|
|
canvas.height = video.videoHeight;
|
|
|
|
|
canvas.width = video.videoWidth;
|
|
|
|
|
canvas
|
|
|
|
|
.getContext("2d", BASE_2D_CONTEXT_OPTIONS)
|
|
|
|
|
?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
|
|
|
|
|
canvas.toBlob((blob) => {
|
|
|
|
|
if (blob instanceof Blob) {
|
2021-11-20 22:32:19 -08:00
|
|
|
subIcons.push(processDirectory["VideoPlayer"].icon);
|
2021-11-13 22:26:22 -08:00
|
|
|
getInfoByFileExtension(URL.createObjectURL(blob));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
ONE_TIME_PASSIVE_EVENT
|
|
|
|
|
);
|
|
|
|
|
video.src = bufferToUrl(contents);
|
|
|
|
|
video.load();
|
|
|
|
|
}
|
|
|
|
|
})
|
2021-10-23 22:13:17 -07:00
|
|
|
);
|
2021-07-03 21:46:40 -07:00
|
|
|
} else if (extension === ".mp3") {
|
2021-10-09 23:24:09 -07:00
|
|
|
getInfoByFileExtension(
|
2021-10-23 22:13:17 -07:00
|
|
|
`/System/Icons/${extensions[".mp3"].icon as string}.png`,
|
|
|
|
|
() =>
|
|
|
|
|
fs.readFile(path, (error, contents = EMPTY_BUFFER) => {
|
|
|
|
|
if (!error) {
|
|
|
|
|
import("music-metadata-browser").then(
|
|
|
|
|
({ parseBuffer, selectCover }) =>
|
|
|
|
|
parseBuffer(
|
|
|
|
|
contents,
|
|
|
|
|
{
|
|
|
|
|
mimeType: MP3_MIME_TYPE,
|
|
|
|
|
size: contents.length,
|
|
|
|
|
},
|
|
|
|
|
{ skipPostHeaders: true }
|
|
|
|
|
).then(({ common: { picture } = {} }) => {
|
|
|
|
|
const { data: coverPicture } = selectCover(picture) || {};
|
|
|
|
|
|
|
|
|
|
if (coverPicture) {
|
|
|
|
|
getInfoByFileExtension(bufferToUrl(coverPicture));
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
})
|
2021-10-09 23:24:09 -07:00
|
|
|
);
|
2021-07-03 21:46:40 -07:00
|
|
|
} else {
|
|
|
|
|
getInfoByFileExtension();
|
|
|
|
|
}
|
|
|
|
|
};
|
2021-07-17 22:46:03 -07:00
|
|
|
|
|
|
|
|
export const filterSystemFiles =
|
|
|
|
|
(directory: string) =>
|
|
|
|
|
(file: string): boolean =>
|
2021-09-25 21:44:16 -07:00
|
|
|
!SYSTEM_PATHS.has(join(directory, file)) && !SYSTEM_FILES.has(file);
|
2021-10-02 22:41:53 -07:00
|
|
|
|
|
|
|
|
type WrapData = {
|
|
|
|
|
lines: string[];
|
|
|
|
|
width: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const canvasContexts: Record<string, CanvasRenderingContext2D> = {};
|
|
|
|
|
|
|
|
|
|
const measureText = (
|
|
|
|
|
text: string,
|
|
|
|
|
fontSize: string,
|
|
|
|
|
fontFamily: string
|
2021-11-06 21:41:33 -07:00
|
|
|
): number => {
|
2021-10-02 22:41:53 -07:00
|
|
|
const font = `${fontSize} ${fontFamily}`;
|
|
|
|
|
|
|
|
|
|
if (!canvasContexts[font]) {
|
|
|
|
|
const canvas = document.createElement("canvas");
|
|
|
|
|
const context = canvas.getContext(
|
|
|
|
|
"2d",
|
|
|
|
|
BASE_2D_CONTEXT_OPTIONS
|
|
|
|
|
) as CanvasRenderingContext2D;
|
|
|
|
|
|
|
|
|
|
context.font = font;
|
|
|
|
|
canvasContexts[font] = context;
|
|
|
|
|
}
|
2021-11-06 21:41:33 -07:00
|
|
|
const { actualBoundingBoxLeft, actualBoundingBoxRight } =
|
|
|
|
|
canvasContexts[font].measureText(text);
|
2021-10-02 22:41:53 -07:00
|
|
|
|
2021-11-06 21:41:33 -07:00
|
|
|
return Math.abs(actualBoundingBoxLeft) + Math.abs(actualBoundingBoxRight);
|
2021-10-02 22:41:53 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getTextWrapData = (
|
|
|
|
|
text: string,
|
|
|
|
|
fontSize: string,
|
|
|
|
|
fontFamily: string,
|
|
|
|
|
maxWidth?: number
|
|
|
|
|
): WrapData => {
|
|
|
|
|
const lines = [""];
|
|
|
|
|
|
2021-11-06 21:41:33 -07:00
|
|
|
const totalWidth = measureText(text, fontSize, fontFamily);
|
2021-10-02 22:41:53 -07:00
|
|
|
|
|
|
|
|
if (!maxWidth) return { lines: [text], width: totalWidth };
|
|
|
|
|
|
|
|
|
|
if (totalWidth > maxWidth) {
|
2021-11-27 21:12:39 -08:00
|
|
|
const words = text.split(" ");
|
|
|
|
|
|
2021-10-02 22:41:53 -07:00
|
|
|
[...text].forEach((character) => {
|
2021-11-27 21:12:39 -08:00
|
|
|
const lineIndex = lines.length - 1;
|
|
|
|
|
const lineText = `${lines[lineIndex]}${character}`;
|
2021-11-06 21:41:33 -07:00
|
|
|
const lineWidth = measureText(lineText, fontSize, fontFamily);
|
2021-10-02 22:41:53 -07:00
|
|
|
|
|
|
|
|
if (lineWidth > maxWidth) {
|
2021-11-27 21:12:39 -08:00
|
|
|
const spacesInLine = lineText.split(" ").length - 1;
|
|
|
|
|
const lineWithWords = words.splice(0, spacesInLine).join(" ");
|
|
|
|
|
|
2021-11-27 21:17:44 -08:00
|
|
|
if (
|
|
|
|
|
lines.length === 1 &&
|
|
|
|
|
spacesInLine > 0 &&
|
|
|
|
|
lines[0] !== lineWithWords
|
|
|
|
|
) {
|
2021-11-27 21:12:39 -08:00
|
|
|
lines[0] = lineText.slice(0, lineWithWords.length);
|
|
|
|
|
lines.push(lineText.slice(lineWithWords.length));
|
|
|
|
|
} else {
|
|
|
|
|
lines.push(character);
|
|
|
|
|
}
|
2021-10-02 22:41:53 -07:00
|
|
|
} else {
|
2021-11-27 21:12:39 -08:00
|
|
|
lines[lineIndex] = lineText;
|
2021-10-02 22:41:53 -07:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
lines,
|
|
|
|
|
width: Math.min(maxWidth, totalWidth),
|
|
|
|
|
};
|
|
|
|
|
};
|