Skip to content

Commit 2ca7670

Browse files
committed
Initial commit
0 parents  commit 2ca7670

21 files changed

Lines changed: 7673 additions & 0 deletions

.gitattributes

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*.png filter=lfs diff=lfs merge=lfs -text
2+
*.jpg filter=lfs diff=lfs merge=lfs -text
3+
*.jpeg filter=lfs diff=lfs merge=lfs -text
4+
src/assets/** filter=lfs diff=lfs merge=lfs -text
5+
external/** filter=lfs diff=lfs merge=lfs -text

.github/workflows/deploy.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: true
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
with:
24+
lfs: true
25+
26+
- name: Setup Node
27+
uses: actions/setup-node@v4
28+
with:
29+
node-version: 20
30+
cache: npm
31+
32+
- name: Install dependencies
33+
run: npm ci
34+
35+
- name: Build
36+
run: npm run build
37+
38+
- name: Upload artifact
39+
uses: actions/upload-pages-artifact@v3
40+
with:
41+
path: ./dist
42+
43+
deploy:
44+
environment:
45+
name: github-pages
46+
url: ${{ steps.deployment.outputs.page_url }}
47+
runs-on: ubuntu-latest
48+
needs: build
49+
steps:
50+
- name: Deploy to GitHub Pages
51+
id: deployment
52+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
dist
3+
tmp
4+
.DS_Store

LICENSE

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Copyright 2025 8th Wall, Inc.
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
4+
associated documentation files (the "Software"), to deal in the Software without restriction,
5+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
6+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
7+
furnished to do so, subject to the following conditions:
8+
9+
The above copyright notice and this permission notice shall be included in all copies or substantial
10+
portions of the Software.
11+
12+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
13+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
14+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
15+
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
16+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Studio: Face Effects
2+
3+
This project demonstrates how to use Face Effects to anchor virtual content to a face.
4+
5+
![Preview of the face effects project open in the editor](./src/assets/preview.jpg)
6+
7+
<details><summary>Try it out</summary>
8+
9+
https://8thwall.org/studio-face-effects-example/
10+
11+
<img alt="QR Code for the preview link" src="https://8th.io/qr?v=2&url=https://8thwall.org/studio-face-effects-example/" width=250 height=250 />
12+
13+
</details>
14+
15+
## Usage
16+
17+
1. [Install the Desktop App](https://8thwall.org/downloads)
18+
2. On this repository, click Code > Download zip
19+
3. Unzip the folder to the location you'd like to work in
20+
4. In the desktop app, click "Open" and select the folder
21+
5. To connect to a mobile device, follow [these instructions](https://8th.io/test-on-mobile)
22+
6. Recommended: Track your files using [git](https://git-scm.com/about) to avoid losing progress
23+
24+
## Deployment
25+
26+
This project contains Github Actions configuration for deployment to Github Pages, which triggers automatically by pushing the `main` branch. You can also follow the publishing instructions here: https://8thwall.org/docs/getting-started/publishing to publish to any other web host.
27+
28+
## Questions?
29+
30+
Please raise any questions on [Github Discussions](https://github.com/orgs/8thwall/discussions) or join the [Discord](https://8th.io/discord) to connect with the community.
31+

config/LICENSE

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Copyright 2025 Niantic Spatial, Inc.
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
4+
associated documentation files (the "Software"), to deal in the Software without restriction,
5+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
6+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
7+
furnished to do so, subject to the following conditions:
8+
9+
The above copyright notice and this permission notice shall be included in all copies or substantial
10+
portions of the Software.
11+
12+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
13+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
14+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
15+
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
16+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

config/asset-loader.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const path = require('path')
2+
3+
const toUnixPath = process.platform === 'win32'
4+
? p => p.replace(/\\/g, '/').replace(/\/+/g, '/')
5+
: p => p
6+
7+
const makeExport = srcPath => `module.exports = ${JSON.stringify(toUnixPath(srcPath))};`
8+
9+
// Export the asset path as asset content
10+
function assetLoader() {
11+
const srcPath = path.relative(this.rootContext, this.resourcePath)
12+
13+
// Export the relative path for the asset file
14+
return makeExport(srcPath)
15+
}
16+
17+
module.exports = assetLoader

config/dev8-plugin.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const HtmlWebpackPlugin = require('html-webpack-plugin')
2+
3+
const createDev8Plugin = ({src}) => ({
4+
apply: (compiler) => {
5+
if (!src) {
6+
throw new Error('createDev8Plugin called without src')
7+
}
8+
compiler.hooks.compilation.tap('Dev8Plugin', (compilation) => {
9+
HtmlWebpackPlugin.getCompilationHooks(compilation).beforeEmit.tap(
10+
'Dev8Plugin',
11+
(data) => {
12+
data.html = data.html.replace(
13+
'</head>',
14+
` <script crossorigin="anonymous" src="${src}"></script>\n</head>`
15+
)
16+
return data
17+
}
18+
)
19+
})
20+
},
21+
})
22+
23+
module.exports = createDev8Plugin

config/entry-plugin.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
const path = require('path')
2+
const VirtualModulesPlugin = require('webpack-virtual-modules')
3+
4+
const isCodeFile = file => file.endsWith('.ts') || file.endsWith('.js')
5+
const IGNORED_FOLDERS = ['assets', '.dependencies']
6+
7+
const SCENE_INIT_CONTENT = `
8+
import scene from './.expanse.json'
9+
10+
delete scene.history
11+
delete scene.historyVersion
12+
13+
window.ecs.application.init(scene)
14+
15+
if (module.hot) {
16+
const isInline = window.location.href.includes('liveSyncMode=inline')
17+
18+
const handler = isInline
19+
? () => { }
20+
: async () => {
21+
const updatedScene = (await import('./.expanse.json')).default
22+
23+
delete updatedScene.history
24+
delete updatedScene.historyVersion
25+
26+
window.ecs.application.getScene().updateBaseObjects(updatedScene.objects)
27+
window.ecs.application.getScene().updateDebug(updatedScene)
28+
}
29+
30+
module.hot.accept('./.expanse.json', handler)
31+
}`
32+
33+
const createVirtualEntryPlugin = (options = {}) => {
34+
const {srcDir} = options
35+
const virtualModules = new VirtualModulesPlugin()
36+
const importedFiles = new Set()
37+
const ignoredFoldersPaths = IGNORED_FOLDERS.map(folder => path.join(srcDir, folder))
38+
39+
let hasInit = false
40+
let watcher = null
41+
42+
const removeImports = (files) => {
43+
files.forEach(file => importedFiles.delete(file))
44+
}
45+
46+
const updateVirtualModules = () => {
47+
const appFiles = Array.from(importedFiles).filter(
48+
file => file === path.join(srcDir, 'app.js') || file === path.join(srcDir, 'app.ts')
49+
)
50+
const otherFiles = Array.from(importedFiles).filter(
51+
file => !appFiles.includes(file)
52+
)
53+
54+
const imports = [...appFiles, ...otherFiles]
55+
.map((file) => {
56+
const relativePath = path.relative(srcDir, file)
57+
return `import ${JSON.stringify(`./${relativePath.replace(/\\/g, '/')}`)}`
58+
})
59+
.join('\n')
60+
61+
const content = `${imports}\n${SCENE_INIT_CONTENT}`
62+
63+
virtualModules.writeModule(path.join(srcDir, 'entry.js'), content)
64+
}
65+
66+
const initFileWatcher = async () => {
67+
if (watcher) {
68+
return
69+
}
70+
71+
try {
72+
const chokidar = await import('chokidar')
73+
watcher = chokidar.default.watch(srcDir, {
74+
ignored: filePath => ignoredFoldersPaths.some(folder => filePath.startsWith(folder)),
75+
persistent: true,
76+
})
77+
78+
watcher.on('add', (filePath) => {
79+
if (isCodeFile(filePath)) {
80+
importedFiles.add(filePath)
81+
updateVirtualModules()
82+
}
83+
})
84+
} catch (err) {
85+
// chokidar not available, file watching disabled
86+
}
87+
}
88+
89+
const closeFileWatcher = () => {
90+
if (watcher) {
91+
watcher.close()
92+
watcher = null
93+
}
94+
}
95+
96+
const getCodeFiles = (fs, dir) => {
97+
const files = fs.readdirSync(dir)
98+
files.forEach((file) => {
99+
const fullPath = path.join(dir, file)
100+
try {
101+
if (fs.lstatSync(fullPath).isDirectory()) {
102+
if (!IGNORED_FOLDERS.includes(file)) {
103+
getCodeFiles(fs, fullPath)
104+
}
105+
} else if (isCodeFile(fullPath)) {
106+
importedFiles.add(fullPath)
107+
}
108+
} catch (err) {
109+
// ignore virtual files
110+
}
111+
})
112+
}
113+
114+
return {
115+
apply: (compiler) => {
116+
compiler.hooks.watchRun.tapAsync('VirtualEntryPlugin', async (compilation, callback) => {
117+
const {removedFiles} = compilation
118+
119+
if (!hasInit) {
120+
hasInit = true
121+
updateVirtualModules()
122+
await initFileWatcher()
123+
}
124+
125+
if (removedFiles) {
126+
const removedCodeFiles = Array.from(removedFiles).filter(isCodeFile)
127+
if (removedCodeFiles.length > 0) {
128+
removeImports(removedCodeFiles)
129+
updateVirtualModules()
130+
}
131+
}
132+
133+
callback()
134+
})
135+
136+
compiler.hooks.shutdown.tap('VirtualEntryPlugin', () => {
137+
closeFileWatcher()
138+
})
139+
140+
compiler.hooks.beforeCompile.tapAsync('VirtualEntryPlugin', (_, callback) => {
141+
if (!compiler.watching && !hasInit) {
142+
getCodeFiles(compiler.inputFileSystem, srcDir)
143+
updateVirtualModules()
144+
hasInit = true
145+
}
146+
callback()
147+
})
148+
149+
virtualModules.apply(compiler)
150+
},
151+
}
152+
}
153+
154+
module.exports = createVirtualEntryPlugin

config/types/assets.d.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
declare module './assets/*' {
2+
const content: string
3+
export default content
4+
}
5+
6+
declare module '../assets/*' {
7+
const content: string
8+
export default content
9+
}
10+
11+
declare module '../../assets/*' {
12+
const content: string
13+
export default content
14+
}
15+
16+
declare module '../../../assets/*' {
17+
const content: string
18+
export default content
19+
}
20+
21+
declare module '../../../../assets/*' {
22+
const content: string
23+
export default content
24+
}
25+
26+
declare module '../../../../../assets/*' {
27+
const content: string
28+
export default content
29+
}
30+
31+
declare module '../../../../../../assets/*' {
32+
const content: string
33+
export default content
34+
}
35+
36+
declare module '../../../../../../../assets/*' {
37+
const content: string
38+
export default content
39+
}

0 commit comments

Comments
 (0)