Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MS] Simple file viewers #8837

Merged
merged 1 commit into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cspell/cspell.config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ ignorePaths:
- "**/*.icns"
- "**/.git"
- "**/client/.env.*"
- "**/client/tests/unit/data"
- "**/client/tests/e2e/data"
import:
- "@cspell/dict-fr-fr/cspell-ext.json"
- "@cspell/dict-fr-reforme/cspell-ext.json"
Expand Down
2 changes: 2 additions & 0 deletions .cspell/custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ blockstores
Booyakasha
boto
botocore
bottombar
broadcastable
browserslistrc
Bsas
Expand Down Expand Up @@ -736,6 +737,7 @@ winify
winres
wizz
wksp
wordprocessingml
workdir
workspacefs
wpath
Expand Down
1 change: 1 addition & 0 deletions client/.eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ electron/node_modules
src/views/testing/TestPage.vue
bindings
playwright.config.ts
src/parsec/mock_files
1 change: 1 addition & 0 deletions client/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ package-lock.json
# Auto-generated
src/plugins/libparsec/definitions.ts
src/components.d.ts
src/parsec/mock_files
502 changes: 485 additions & 17 deletions client/package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@
"file-type": "^19.6.0",
"luxon": "^3.4.4",
"megashark-lib": "git+https://github.com/Scille/megashark-lib.git#f65a38f9255bb7b4e1fafe1be3b833b6a1a8f1d4",
"mammoth": "^1.8.0",
"monaco-editor": "^0.52.0",
"pdfjs-dist": "^4.8.69",
"qrcode-vue3": "^1.6.8",
"uuid": "^9.0.1",
"vue": "^3.3.8",
"vue-i18n": "^9.6.5",
"vue-router": "^4.2.5"
"vue-router": "^4.2.5",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
},
"devDependencies": {
"@capacitor/cli": "^5.6.0",
Expand Down
5 changes: 2 additions & 3 deletions client/scripts/file_to_uint8array.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@
parser.add_argument("-o", "--output", default=sys.stdout, help="Where to put the result")
args = parser.parse_args()
content = args.input.read_bytes()
array = ",".join([str(c) for c in content])
result = f"""
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS
array = ", ".join([str(c) for c in content])
result = f"""// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS

/*
Generated automatically with {sys.argv[0]}
Expand Down
95 changes: 93 additions & 2 deletions client/src/common/fileTypes.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS

import {
closeFile,
closeHistoryFile,
FileDescriptor,
FsPath,
openFile,
openFileAt,
Path,
readFile,
readHistoryFile,
WorkspaceHandle,
} from '@/parsec';
import { fileTypeFromBuffer } from 'file-type';
import { DateTime } from 'luxon';

enum FileContentType {
Image = 'image',
Video = 'video',
Audio = 'audio',
Spreadsheet = 'spreadsheet',
Document = 'document',
Text = 'text',
PdfDocument = 'pdf-document',
Unknown = 'unknown',
}

Expand All @@ -19,8 +34,25 @@ interface DetectedFileType {

const IMAGES = ['image/png', 'image/webp', 'image/jpeg', 'image/svg+xml', 'image/bmp', 'image/gif'];
const SPREADSHEETS = ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
const DOCUMENTS = ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
const PDF_DOCUMENTS = ['application/pdf'];
const AUDIOS = ['audio/x-wav', 'audio/mpeg'];
const VIDEOS = ['video/mp4', 'video/mpeg'];

async function detectFileContentType(buffer: Uint8Array): Promise<DetectedFileType> {
const TEXTS = new Map<string, string>([
['xml', 'application/xml'],
['json', 'application/json'],
['js', 'text/javascript'],
['html', 'text/html'],
['htm', 'text/html'],
['txt', 'text/plain'],
['sh', 'application/x-sh'],
['csv', 'text/csv'],
['css', 'text/css'],
['py', 'text/x-python'],
]);

async function detectFileContentTypeFromBuffer(buffer: Uint8Array): Promise<DetectedFileType> {
const result = await fileTypeFromBuffer(buffer);

if (!result) {
Expand All @@ -33,9 +65,68 @@ async function detectFileContentType(buffer: Uint8Array): Promise<DetectedFileTy
if (SPREADSHEETS.includes(result.mime)) {
return { type: FileContentType.Spreadsheet, extension: result.ext, mimeType: result.mime };
}
if (DOCUMENTS.includes(result.mime)) {
return { type: FileContentType.Document, extension: result.ext, mimeType: result.mime };
}
if (PDF_DOCUMENTS.includes(result.mime)) {
return { type: FileContentType.PdfDocument, extension: result.ext, mimeType: result.mime };
}
if (AUDIOS.includes(result.mime)) {
return { type: FileContentType.Audio, extension: result.ext, mimeType: result.mime };
}
if (VIDEOS.includes(result.mime)) {
return { type: FileContentType.Video, extension: result.ext, mimeType: result.mime };
}
console.log(`Unhandled mimetype ${result.mime}`);

return { type: FileContentType.Unknown, extension: result.ext, mimeType: result.mime };
}

export { DetectedFileType, FileContentType, detectFileContentType };
async function detectFileContentType(workspaceHandle: WorkspaceHandle, path: FsPath, at?: DateTime): Promise<DetectedFileType | undefined> {
const fileName = await Path.filename(path);

if (!fileName) {
return;
}
const ext = Path.getFileExtension(fileName).toLocaleLowerCase();

if (TEXTS.has(ext)) {
return { type: FileContentType.Text, extension: ext, mimeType: TEXTS.get(ext) as string };
}

const READ_CHUNK_SIZE = 512;
let fd: FileDescriptor | null = null;
try {
let openResult;
if (at) {
openResult = await openFileAt(workspaceHandle, path, at);
} else {
openResult = await openFile(workspaceHandle, path, { read: true });
}
if (!openResult.ok) {
return;
}
fd = openResult.value;
let readResult;
if (at) {
readResult = await readHistoryFile(workspaceHandle, fd, 0, READ_CHUNK_SIZE);
} else {
readResult = await readFile(workspaceHandle, fd, 0, READ_CHUNK_SIZE);
}
if (!readResult.ok) {
return;
}
const buffer = new Uint8Array(readResult.value);
return await detectFileContentTypeFromBuffer(buffer);
} finally {
if (fd) {
if (at) {
closeHistoryFile(workspaceHandle, fd);
} else {
closeFile(workspaceHandle, fd);
}
}
}
}

export { DetectedFileType, detectFileContentType, detectFileContentTypeFromBuffer, FileContentType };
6 changes: 5 additions & 1 deletion client/src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@
},
"myProfile": "My profile",
"recoveryExport": "Recovery file",
"history": "History"
"history": "History",
"viewer": "File viewer"
},
"previous": "Go back",
"invitations": {
Expand Down Expand Up @@ -1671,5 +1672,8 @@
"loading": "Loading...",
"workspace": "Workspace:",
"date": "Preview date:"
},
"fileViewers": {
"openWithDefault": "Open with default application"
}
}
6 changes: 5 additions & 1 deletion client/src/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@
},
"myProfile": "Mon profil",
"recoveryExport": "Fichier de récupération",
"history": "Historique"
"history": "Historique",
"viewer": "Aperçu du fichier"
},
"previous": "Précédent",
"invitations": {
Expand Down Expand Up @@ -1671,5 +1672,8 @@
"loading": "Chargement...",
"workspace": "Espace de travail :",
"date": "Date de prévisualisation :"
},
"fileViewers": {
"openWithDefault": "Ouvrir avec l'application par défaut"
}
}
15 changes: 15 additions & 0 deletions client/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import { Information, InformationDataType, InformationLevel, InformationManager,
import { InjectionProvider, InjectionProviderKey } from '@/services/injectionProvider';
import { Sentry } from '@/services/sentry';
import { Answer, Base64, I18n, Locale, MegaSharkPlugin, ThemeManager, Validity, askQuestion } from 'megashark-lib';
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import * as pdfjs from 'pdfjs-dist';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker?worker&url';

enum AppState {
Ready = 'ready',
Expand All @@ -51,6 +54,16 @@ function preventRightClick(): void {
});
}

async function initViewers(): Promise<void> {
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;

self.MonacoEnvironment = {
getWorker: function (_workerId, _label): Worker {
return new editorWorker();
},
};
}

async function setupApp(): Promise<void> {
await storageManagerInstance.init();
const storageManager = storageManagerInstance.get();
Expand Down Expand Up @@ -94,6 +107,8 @@ async function setupApp(): Promise<void> {
app.provide(InjectionProviderKey, injectionProvider);
app.provide(HotkeyManagerKey, hotkeyManager);

await initViewers();

// We get the app element
const appElem = document.getElementById('app');
if (!appElem) {
Expand Down
22 changes: 20 additions & 2 deletions client/src/parsec/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ export async function entryStat(workspaceHandle: WorkspaceHandle, path: FsPath):
let entry: MockEntry;

if (path !== '/' && fileName.startsWith('File_')) {
entry = await generateFile(path, `${MOCK_FILE_ID}`);
entry = await generateFile(path, { parentId: `${MOCK_FILE_ID}`, fileName: fileName });
} else {
entry = await generateFolder(path, `${MOCK_FILE_ID}`);
entry = await generateFolder(path, { parentId: `${MOCK_FILE_ID}`, fileName: fileName });
}
(entry as any as EntryStat).baseVersion = entry.version;
(entry as any as EntryStat).confinementPoint = null;
Expand Down Expand Up @@ -330,6 +330,24 @@ export async function readFile(
case 'png':
console.log('Using PNG content');
return { ok: true, value: MockFiles.PNG };
case 'docx':
console.log('Using DOCX content');
return { ok: true, value: MockFiles.DOCX };
case 'txt':
console.log('Using TXT content');
return { ok: true, value: MockFiles.TXT };
case 'py':
console.log('Using PY content');
return { ok: true, value: MockFiles.PY };
case 'pdf':
console.log('Using PDF content');
return { ok: true, value: MockFiles.PDF };
case 'mp3':
console.log('Using MP3 content');
return { ok: true, value: MockFiles.MP3 };
case 'mp4':
console.log('Using MP4 content');
return { ok: true, value: MockFiles.MP4 };
}
console.log('Using default file content');
return {
Expand Down
Loading