-
Notifications
You must be signed in to change notification settings - Fork 181
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
gergo/previews #3765
base: main
Are you sure you want to change the base?
gergo/previews #3765
Changes from 10 commits
aa2756e
8a7e4fa
c4a4533
3d27c06
f29b78d
0cf172e
1804940
e4ef524
314cf64
9590220
febb0bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# preview-frontend | ||
|
||
This app is the frontend of the preview service. | ||
|
||
It is built into static assets and bundled into the final preview service. | ||
|
||
To test the app locally, run `yarn dev` and call the functions available on the global `window` object. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { baseConfigs, globals, getESMDirname } from '../../eslint.config.mjs' | ||
import tseslint from 'typescript-eslint' | ||
|
||
/** | ||
* @type {Array<import('eslint').Linter.FlatConfig>} | ||
*/ | ||
const configs = [ | ||
...baseConfigs, | ||
{ | ||
files: ['**/*.js'], | ||
languageOptions: { | ||
sourceType: 'module' | ||
} | ||
}, | ||
{ | ||
files: ['*.{js,cjs,mjs,ts}'], | ||
languageOptions: { | ||
globals: { | ||
...globals.node | ||
} | ||
} | ||
}, | ||
{ | ||
files: ['**/*.src'], | ||
languageOptions: { | ||
globals: { | ||
...globals.browser | ||
} | ||
} | ||
}, | ||
...tseslint.configs.recommendedTypeChecked.map((c) => ({ | ||
...c, | ||
files: [...(c.files || []), '**/*.ts', '**/*.d.ts'] | ||
})), | ||
{ | ||
files: ['**/*.ts', '**/*.d.ts'], | ||
languageOptions: { | ||
parserOptions: { | ||
tsconfigRootDir: getESMDirname(import.meta.url), | ||
project: './tsconfig.json' | ||
} | ||
}, | ||
rules: { | ||
'@typescript-eslint/no-unused-vars': 'off', | ||
'@typescript-eslint/no-non-null-assertion': 'error', | ||
'@typescript-eslint/no-base-to-string': 'off', // too restrictive | ||
'@typescript-eslint/restrict-template-expressions': 'off', // too restrictive | ||
'@typescript-eslint/no-unsafe-enum-comparison': 'off', // too restrictive | ||
'@typescript-eslint/require-await': 'off', // too restrictive | ||
'@typescript-eslint/unbound-method': 'off', // too restrictive | ||
'@typescript-eslint/no-misused-promises': 'off' | ||
} | ||
}, | ||
{ | ||
rules: { | ||
'no-console': 'off' | ||
} | ||
} | ||
] | ||
|
||
export default configs |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width,initial-scale=1.0" /> | ||
<title>Speckle Viewer</title> | ||
<style> | ||
body { | ||
font-family: 'Space Mono', monospace !important; | ||
margin: 0px; | ||
} | ||
button { | ||
font-family: 'Space Mono', monospace !important; | ||
border-color: #0a66ff; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div | ||
id="renderer" | ||
style="width: 700px; height: 400px; left: 0px; top: 0px; position: absolute" | ||
></div> | ||
<script type="module" defer="defer" src="/src/main.ts"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
"name": "@speckle/preview-frontend", | ||
"description": "Webapp to Generate PNG previews of Speckle objects", | ||
"private": true, | ||
"homepage": "https://speckle.systems", | ||
"type": "module", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/specklesystems/speckle-server.git", | ||
"directory": "packages/preview-frontend" | ||
}, | ||
"scripts": { | ||
"dev": "vite", | ||
"build": "tsc && vite build", | ||
"preview": "vite preview", | ||
"lint": "yarn lint:tsc && yarn lint:eslint", | ||
"lint:tsc": "tsc --noEmit", | ||
"lint:eslint": "eslint ." | ||
}, | ||
"dependencies": { | ||
"@speckle/shared": "workspace:^", | ||
"@speckle/viewer": "workspace:^" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^9.4.0", | ||
"eslint-config-prettier": "^9.1.0", | ||
"typescript": "^5.7.2", | ||
"typescript-eslint": "^7.12.0", | ||
"vite": "^6.0.0" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { | ||
Load, | ||
LoadArgs, | ||
PreviewGenerator, | ||
PreviewResult, | ||
TakeScreenshot | ||
} from '@speckle/shared/dist/esm/previews/interface.js' | ||
import { | ||
Viewer, | ||
DefaultViewerParams, | ||
SpeckleLoader, | ||
UrlHelper, | ||
UpdateFlags | ||
} from '@speckle/viewer' | ||
import { CameraController } from '@speckle/viewer' | ||
|
||
declare global { | ||
interface Window extends PreviewGenerator {} | ||
} | ||
|
||
let viewer: Viewer | undefined = undefined | ||
|
||
const init = async (): Promise<Viewer> => { | ||
/** Get the HTML container */ | ||
const container = document.getElementById('renderer') as HTMLElement | ||
|
||
/** Configure the viewer params */ | ||
const params = DefaultViewerParams | ||
params.showStats = false | ||
params.verbose = false | ||
|
||
/** Create Viewer instance */ | ||
const viewer = new Viewer(container, params) | ||
/** Initialise the viewer */ | ||
await viewer.init() | ||
|
||
/** Add the stock camera controller extension */ | ||
viewer.createExtension(CameraController) | ||
return viewer | ||
} | ||
|
||
const load: Load = async ({ url, token }: LoadArgs) => { | ||
if (!viewer) viewer = await init() | ||
/** Create a loader for the speckle stream */ | ||
const resourceUrls = await UrlHelper.getResourceUrls(url, token) | ||
for (const resourceUrl of resourceUrls) { | ||
const loader = new SpeckleLoader(viewer.getWorldTree(), resourceUrl, token) | ||
/** Load the speckle data */ | ||
await viewer.loadObject(loader, true) | ||
} | ||
} | ||
|
||
window.load = load | ||
|
||
// TODO: replace with sleep from speckle/shared | ||
const waitForAnimation = async (ms = 70) => | ||
await new Promise((resolve) => { | ||
setTimeout(resolve, ms) | ||
}) | ||
|
||
const takeScreenshot: TakeScreenshot = async () => { | ||
if (!viewer) viewer = await init() | ||
const ret: PreviewResult = { | ||
duration: 0, | ||
screenshots: {} | ||
} | ||
|
||
const t0 = Date.now() | ||
|
||
viewer.resize() | ||
const cameraController = viewer.getExtension(CameraController) | ||
cameraController.setCameraView([], false, 0.95) | ||
await waitForAnimation(100) | ||
|
||
for (let i = 0; i < 24; i++) { | ||
cameraController.setCameraView({ azimuth: Math.PI / 12, polar: 0 }, false) | ||
viewer.requestRender(UpdateFlags.RENDER_RESET) | ||
await waitForAnimation(10) | ||
ret.screenshots[i + ''] = await viewer.screenshot() | ||
} | ||
ret.duration = (Date.now() - t0) / 1000 | ||
return ret | ||
} | ||
window.takeScreenshot = takeScreenshot |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/// <reference types="vite/client" /> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "ES2020", | ||
"useDefineForClassFields": true, | ||
"module": "ESNext", | ||
"lib": ["ES2020", "DOM", "DOM.Iterable"], | ||
"skipLibCheck": true, | ||
|
||
/* Bundler mode */ | ||
"moduleResolution": "Bundler", | ||
"allowImportingTsExtensions": true, | ||
"isolatedModules": true, | ||
"moduleDetection": "force", | ||
"noEmit": true, | ||
|
||
/* Linting */ | ||
"strict": true, | ||
"noUnusedLocals": true, | ||
"noUnusedParameters": true, | ||
"noFallthroughCasesInSwitch": true | ||
}, | ||
"include": ["src"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,7 @@ | ||
PREVIEWS_HEADED='true' | ||
CHROMIUM_EXECUTABLE_PATH='/usr/bin/google-chrome-stable' | ||
USER_DATA_DIR='/tmp/puppeteer' | ||
PG_CONNECTION_STRING='postgres://speckle:[email protected]/speckle' | ||
POSTGRES_MAX_CONNECTIONS_PREVIEW_SERVICE='2' | ||
REDIS_URL='redis://localhost' | ||
PROMETHEUS_METRICS_PORT='9094' | ||
PORT='3001' | ||
LOG_LEVEL='info' | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
public/ | ||
public |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import type { Preview } from '@/domain/domain.js' | ||
import type { Preview } from '../domain/domain.js' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we retain the alias pattern please? i.e. |
||
import type { Knex } from 'knex' | ||
|
||
export type PreviewRow = { id: string; data: Buffer } | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ import { | |
notifyUpdateFactory, | ||
updatePreviewMetadataFactory | ||
} from '@/repositories/objectPreview.js' | ||
import { insertPreviewFactory } from '@/repositories/previews.js' | ||
import { insertPreviewFactory } from '../repositories/previews.js' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, the alias pattern should be used. |
||
import { generateAndStore360PreviewFactory } from '@/services/360preview.js' | ||
import { pollForAndCreatePreviewFactory } from '@/services/pollForPreview.js' | ||
import { throwUncoveredError, wait } from '@speckle/shared' | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
import type { GeneratePreview } from '@/clients/previewService.js' | ||
import type { Angle, ObjectIdentifier, PreviewId } from '@/domain/domain.js' | ||
import type { InsertPreview } from '@/repositories/previews.js' | ||
import type { GeneratePreview } from '../clients/previewService.js' | ||
import type { Angle, ObjectIdentifier, PreviewId } from '../domain/domain.js' | ||
import type { InsertPreview } from '../repositories/previews.js' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. alias pattern should be used. Is there an eslint rule missing? |
||
import crypto from 'crypto' | ||
import { joinImages } from 'join-images' | ||
|
||
|
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're changing this, and to be pedantic, entrypoint should be the binary being executed and cmd the arguments to that statement. So:
And is there a reason for using
dist/main
pattern over thebin/www.js
pattern?