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

gergo/previews #3765

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 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
7 changes: 7 additions & 0 deletions packages/preview-frontend/README.md
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.
61 changes: 61 additions & 0 deletions packages/preview-frontend/eslint.config.mjs
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
25 changes: 25 additions & 0 deletions packages/preview-frontend/index.html
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>
31 changes: 31 additions & 0 deletions packages/preview-frontend/package.json
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"
}
}
84 changes: 84 additions & 0 deletions packages/preview-frontend/src/main.ts
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
1 change: 1 addition & 0 deletions packages/preview-frontend/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
23 changes: 23 additions & 0 deletions packages/preview-frontend/tsconfig.json
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"]
}
3 changes: 1 addition & 2 deletions packages/preview-service/.env.example
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'
Expand Down
2 changes: 1 addition & 1 deletion packages/preview-service/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
public/
public
8 changes: 4 additions & 4 deletions packages/preview-service/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ COPY package.json yarn.lock ./

# Only copy in the relevant package.json files for the dependencies
COPY packages/frontend-2/type-augmentations/stubs ./packages/frontend-2/type-augmentations/stubs/
COPY packages/preview-frontend/package.json ./packages/preview-frontend/
COPY packages/preview-service/package.json ./packages/preview-service/
COPY packages/viewer/package.json ./packages/viewer/
COPY packages/objectloader/package.json ./packages/objectloader/
Expand All @@ -31,6 +32,7 @@ RUN yarn workspaces focus -A && yarn
COPY packages/shared ./packages/shared/
COPY packages/objectloader ./packages/objectloader/
COPY packages/viewer ./packages/viewer/
COPY packages/preview-frontend ./packages/preview-frontend/
COPY packages/preview-service ./packages/preview-service/

# This way the foreach only builds the frontend and its deps
Expand Down Expand Up @@ -77,15 +79,13 @@ COPY .yarn ./.yarn
COPY package.json yarn.lock ./

# Only copy in the relevant package.json files for the dependencies
COPY packages/frontend-2/type-augmentations/stubs ./packages/frontend-2/type-augmentations/stubs/
COPY packages/preview-service/package.json ./packages/preview-service/

WORKDIR /speckle-server/packages

COPY --from=build-stage /speckle-server/packages/shared ./shared
COPY --from=build-stage /speckle-server/packages/objectloader ./objectloader
COPY --from=build-stage /speckle-server/packages/viewer ./viewer
COPY --from=build-stage /speckle-server/packages/preview-service ./preview-service
COPY --from=build-stage /speckle-server/packages/preview-frontend/dist ./preview-service/public

WORKDIR /speckle-server/packages/preview-service

Expand Down Expand Up @@ -125,4 +125,4 @@ RUN apt-get update && \
# Run everything after as non-privileged user.
USER pptruser

ENTRYPOINT [ "tini", "--", "node", "--loader=./dist/src/aliasLoader.js", "bin/www.js" ]
CMD [ "tini", "--", "node", "dist/main" ]
Copy link
Contributor

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:

ENTRYPOINT [ "tini", "--", "node"]
CMD ["dist/main"]

And is there a reason for using dist/main pattern over the bin/www.js pattern?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the _bak directory? Is it a backup to be removed?

File renamed without changes.
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'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we retain the alias pattern please? i.e. @/domain/domain.js would be preferable.

import type { Knex } from 'knex'

export type PreviewRow = { id: string; data: Buffer }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
notifyUpdateFactory,
updatePreviewMetadataFactory
} from '@/repositories/objectPreview.js'
import { insertPreviewFactory } from '@/repositories/previews.js'
import { insertPreviewFactory } from '../repositories/previews.js'
Copy link
Contributor

Choose a reason for hiding this comment

The 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'
Expand Down
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'
Copy link
Contributor

Choose a reason for hiding this comment

The 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'

Expand Down
2 changes: 0 additions & 2 deletions packages/preview-service/bin/www.js

This file was deleted.

Binary file not shown.
Loading