Compare commits
No commits in common. "master" and "feat/godot" have entirely different histories.
master
...
feat/godot
3
.gitignore
vendored
|
|
@ -1,3 +1,2 @@
|
|||
node_modules
|
||||
manyfold/usr
|
||||
manyfold/.env
|
||||
manyfold/usr/public/webxr/node_modules
|
||||
|
|
|
|||
3
.gitmodules
vendored
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "godot"]
|
||||
path = godot
|
||||
url = ../xrforge-godot.git
|
||||
|
|
@ -2,15 +2,13 @@
|
|||
|
||||
> Self-sovereign **XR Experiences** for teams & organisations, powered by FOSS ♥
|
||||
|
||||

|
||||

|
||||
|
||||
* View [index.html](https://coderofsalvation.codeberg.page/xrforge) for the official docs
|
||||
* Click [here](manyfold/README.md) for backend-installation instructions.
|
||||
Click [here](manyfold/README.md) for backend-installation instructions.
|
||||
|
||||
Powered by:
|
||||
|
||||
* [NIX](https://nixos.org) for reproducibility and reliability
|
||||
* [JanusWeb](https://github.com/jbaicoianu/janusweb) for browsing JML / JanusXR immersive web-layer
|
||||
* [XR Fragments](https://xrfragment.org) for interoperability between XR experiences
|
||||
* [Manyfold](https://manyfold.app) for curating & collaborating XR experiences
|
||||
* [Godot](https://godot.org) & [Blender](https://blender.org) for editing XR experiences
|
||||
|
|
|
|||
1
godot
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 79e8669ca609782aa6fa9eeed259d9621697f460
|
||||
3836
index.html
|
|
@ -16,8 +16,6 @@ $ mkdir mnt config
|
|||
$ docker run -t xrforge docker.io/coderofsalvation/xrforge:latest -v ./mnt:/mnt -v ./config:/config
|
||||
```
|
||||
|
||||
> To scan all (mounted) libraries run this once: `$ docker exec xrforge /manyfold/cli/manyfold.sh scan_libraries`
|
||||
|
||||
# Build & Run the container-image
|
||||
|
||||
> **NOTE**: [nix](https://nixos.org) is used to promote reproducability-over-repeatability
|
||||
|
|
@ -26,7 +24,7 @@ $ docker run -t xrforge docker.io/coderofsalvation/xrforge:latest -v ./mnt:/mnt
|
|||
$ git clone --recurse-submodules --depth 1 https://codeberg.org/coderofsalvation/xrforge.git
|
||||
$ cd xrforge
|
||||
$ docker load < $(nix-build nix/docker.nix)
|
||||
$ docker run $(manyfold/cli/manyfold run) # generates a dockercmd with sane env-flags
|
||||
$ manyfold/cli/manyfold run
|
||||
|
||||
[xrforge] podman detected..starting OCI container
|
||||
+ /run/current-system/sw/bin/podman run -p 8790:3214 -p 8791:3215 --name xrforge -e SECRET_KEY_BASE=lkjwljlkwejrlkjek34k234l -e DATABASE_ADAPTER=sqlite3 -e SUDO_RUN_UNSAFELY=enabled -e MULTIUSER=enabled -e FEDERATION=enabled -e THEME=vapor -e HOMEPAGE=/models -e FEDERATE_DRIVE_CACHE=5s -v ./xrfragment/assets:/mnt/assets/xrfragment/#1 --cap-add SYS_ADMIN --security-opt apparmor:unconfined --device /dev/fuse xrforge
|
||||
|
|
@ -38,11 +36,11 @@ sending incremental file list
|
|||
|
||||
> NOTE: if you don't want the default XR Fragments asset-library, omit `--recurse-submodules` in the git cmd
|
||||
|
||||
To preserve your uploaded models and db, add these docker volume-flags, for example:
|
||||
To preserve your uploaded models, add the `experiences` as a docker volume-flag, for example:
|
||||
|
||||
```
|
||||
$ mkdir experiences config
|
||||
$ docker run -t xrforge docker.io/coderofsalvation/xrforge:latest -v ./config:/config -v ./experiences:/mnt/experiences
|
||||
$ mkdir experiences
|
||||
$ manyfold/cli/manyfold run -v ./experiences:/mnt/experiences
|
||||
|
||||
```
|
||||
|
||||
|
|
@ -50,17 +48,16 @@ $ docker run -t xrforge docker.io/coderofsalvation/xrforge:latest -v ./config:/c
|
|||
|
||||
| environment variable | default | info |
|
||||
|-----------------------|--------------|------------------------|
|
||||
| `APPNAME` | `XRForge` | manyfold instance name |
|
||||
| `APPNAME` | `manyfold` | manyfold instance name |
|
||||
| `HOMEPAGE` | `/models` | show '/models' URL as homepage (use `/` for manyfold default) |
|
||||
| `THEME` | `default` | bootstrap theme |
|
||||
| 'JANUSXR' | `` | run local JanusXR stack (janus-server, janus-gateway, janusweb) |
|
||||
| `AFRAME_VERSION` | `1.7.0` | AFRAME version |
|
||||
| `GODOT_VERSION` | `4.4.1-stable`| godot editor version |
|
||||
| `GODOT_TEMPLATE_ZIP` | `` | godot template zip URL or file (default is empty godot project) |
|
||||
| `RUNTESTS` | `0` | set to `1` to run XRForge related [/test](test) scripts |
|
||||
| `DEV` | `` | enable development mode (disables caching, sets `bin/dev` as entrypoint) |
|
||||
| `NO_PUBLIC_ONLY` | `` | disable public only models |
|
||||
| `NO_OVERLAYFS` | `` | disable the filesystem overlay mechanism |
|
||||
| `NO_DEFAULTDB` | `` | disable the default xrforge db (activates manyfold installer) |
|
||||
| `NO_ASSETS` | `` | disable downloading assets from xrfragment.org repository |
|
||||
| `NO_DELETEBIGFILES` | `` | disable deleting big files which are older than 5 days and bigger than ($currentyear-2020) MB's |
|
||||
| `NO_PACKAGEALL` | `` | don't package all experiences every hour to /usr/src/app/public/experiences.zip |
|
||||
|
|
@ -85,8 +82,6 @@ $ docker run -t xrforge docker.io/coderofsalvation/xrforge:latest -v ./config:/c
|
|||
|
||||
> please modify the password in the settings screen of the webinterface.
|
||||
|
||||
It's also possible to enforce a default sqlite3 db via the `-v ./manyfold.sql:/manyfold/manyfold.sql` container flag.
|
||||
|
||||
# Filesystem overlay-mechanism
|
||||
|
||||
The server-image will boot `manyfold/cli/manyfold.sh boot` and check for directory `/manyfold` (in the container).
|
||||
|
|
@ -189,32 +184,3 @@ For a quick dev-environment run:
|
|||
$ mkdir /dev
|
||||
$ manyfold/cli/manyfold.sh run -e DEV=1
|
||||
```
|
||||
|
||||
# JanusXR
|
||||
|
||||
When running xrforge with the `JANUSXR=1` env-flag, the opensource [JanusXR](https://janusxr.org) stack will be installed and started:
|
||||
|
||||
* [janus-server](https://github.com/janusvr/janus-server) for chat + syncing avatar positions
|
||||
* [janus-gateway](https://github.com/meetecho/janus-gateway) for video/audio
|
||||
* [janusweb](https://github.com/meetecho/janus-gateway) the viewer using the above services
|
||||
|
||||
> NOTE: consider this a fingers-crossed 'rolling release' installation, as this is not officially part of XRForge (just a helper for intranets).
|
||||
|
||||
Note that janus-server exposes a http websocket at port 5566, so you need to configure your reverse proxy as following:
|
||||
|
||||
* https://presence.foo.bar.com => 5566
|
||||
|
||||
> This assumes environment-var `FEDERATE_DRIVE_HOST` is set to `https://foo.bar.com` (`presence` subdomain is automatically prefixed by the installer)
|
||||
|
||||
#### persist JanusXR stack
|
||||
|
||||
When running the container run the following cmds to speed up the boot-time:
|
||||
|
||||
```
|
||||
$ docker cp xrforge:/mnt/janusweb .
|
||||
$ docker cp xrforge:/root/janus-server .
|
||||
```
|
||||
|
||||
then add the following flags to your docker cmd: `-v ./janusweb:/mnt/janusweb -v ./janus-server:/root/janus-server`
|
||||
|
||||
> That way JanusXR does not have to be installed every time during boot
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
#!/bin/sh
|
||||
oci=$(which podman || which docker)
|
||||
test -n "$APPNAME" || export APPNAME=XRForge
|
||||
test -n "$TAGLINE" || export TAGLINE="Publish single-file XR experiences to immersive networks"
|
||||
test -n "$UPLOAD_PATH" || export UPLOAD_PATH=/mnt/experiences
|
||||
test -n "$THEME" || export THEME=slate
|
||||
test -n "$HOMEPAGE" || export HOMEPAGE=/models
|
||||
test -n "$GODOT_VERSION" || export GODOT_VERSION=4.4.1-stable
|
||||
test -n "$CORS_PROXY" || export CORS_PROXY=1
|
||||
test -n "$APPNAME" || APPNAME=xrforge
|
||||
test -n "$UPLOAD_PATH" || UPLOAD_PATH=/mnt/experiences
|
||||
test -n "$THEME" || THEME=slate
|
||||
test -n "$HOMEPAGE" || HOMEPAGE=/models
|
||||
test -n "$GODOT_VERSION" || GODOT_VERSION=4.4.1-stable
|
||||
db=/config/manyfold.sqlite3
|
||||
|
||||
# utility funcs
|
||||
|
|
@ -20,16 +18,14 @@ create_config(){
|
|||
}
|
||||
|
||||
run(){
|
||||
{
|
||||
test -x ${oci} || { echo "warning: not running manyfold OCI container [install podman/docker/etc]"; exit; }
|
||||
${oci} rm -f xrforge
|
||||
} 1>&2
|
||||
#-e NO_OVERLAYFS=true \
|
||||
#-e NO_DEFAULTDB=true \
|
||||
#-e PUBLIC_HOSTNAME=localhost \
|
||||
#-e PUBLIC_PORT=80 \
|
||||
#-e JANUSXR=1 \
|
||||
echo ${oci} run "$@" -p 8790:3214 -p 8791:3215 -p 5566:5566 -p 5577:5577 --name xrforge \
|
||||
test -x ${oci} || { echo "warning: not running manyfold OCI container [install podman/docker/etc]"; exit; }
|
||||
echocolor "[$APPNAME]" "$(basename ${oci}) detected..starting OCI container"
|
||||
${oci} rm -f xrforge
|
||||
#-e NO_OVERLAYFS=true \
|
||||
#-e NO_DEFAULTDB=true \
|
||||
#-e PUBLIC_HOSTNAME=localhost \
|
||||
#-e PUBLIC_PORT=80 \
|
||||
debug ${oci} run "$@" -p 8790:3214 -p 8791:3215 --name xrforge \
|
||||
-e SECRET_KEY_BASE=lkjwljlkwejrlkjek34k234l \
|
||||
-e DATABASE_ADAPTER=sqlite3 \
|
||||
-e FEDERATE_DRIVE_HOST=http://localhost:8791 \
|
||||
|
|
@ -50,7 +46,6 @@ overlayfs(){
|
|||
echocolor "[$APPNAME]" "applying filesystem overlay"
|
||||
cd /manyfold
|
||||
rsync -rvzi * /.
|
||||
#apply_patches
|
||||
}
|
||||
|
||||
# cron-like function using sleep (./manifold.sh infinite 3600 zip -r /backup.zip /)
|
||||
|
|
@ -75,9 +70,8 @@ hook(){
|
|||
logger "$ hook $*"
|
||||
cmd=$1
|
||||
shift
|
||||
|
||||
test -d ~/hook.d/$cmd && {
|
||||
find -L ~/hook.d/$cmd/ -type f -executable -maxdepth 1 | sort -V | while read hook; do
|
||||
find -L ~/hook.d/$cmd/ -type f -executable -maxdepth 1 | while read hook; do
|
||||
logger " |+ hook $hook $*"
|
||||
{ $hook "$@" || true; } 2>&1 | awk '{ gsub(/\/root\/\//,"",$1); $1 = sprintf("%-40s", $1)} 1' | logger
|
||||
done
|
||||
|
|
@ -89,29 +83,10 @@ start_hook_daemon(){
|
|||
# 86400 secs = 1 day 3600 = 1 hour
|
||||
$0 infinite 86400 hook daily &
|
||||
$0 infinite 3600 hook hourly &
|
||||
# trigger hooks when files change in /mnt
|
||||
#find /mnt -type d -mindepth 1 -maxdepth 1 | while read dir; do
|
||||
# echocolor "[$APPNAME]" "listening to inotify events in $dir"
|
||||
# # scan for '/mnt/experiences/creatorname/#234/ MODIFY foo.glb' e.g.
|
||||
# # scan for '/mnt/experiences/creatorname/#234/ MOVED_TO foo.glb' e.g.
|
||||
# inotifywait -r -m $dir | awk '/.*/ { print $0 }; $2 ~ /(CREATE|MODIFY|MOVED_TO|DELETE)/ && $3 ~ /datapackage/ { system("'$0' hook datapackage_"$2" "$1""$3) }' &
|
||||
#done
|
||||
|
||||
## force-trigger processing hooks in /mnt
|
||||
#find /mnt | grep datapackage | xargs -n1 $0 hook inotify_MODIFY
|
||||
}
|
||||
|
||||
apply_patches(){
|
||||
echocolor "[$APPNAME]" "applying patches"
|
||||
for patch_file in /manyfold/patches/*.patch; do
|
||||
if patch -p1 -N --forward < "$patch_file"; then
|
||||
echo "✅ Successfully applied **$(basename "$patch_file")**"
|
||||
else
|
||||
echo "🛑 Failed to apply **$(basename "$patch_file")**"
|
||||
echo "Aborting script. Please inspect the failed patch and resolve conflicts."
|
||||
# Use 'exit' to stop the script on the first failure
|
||||
exit 1
|
||||
fi
|
||||
# trigger hooks when files change in /mnt/experiences
|
||||
find /mnt -type d -mindepth 1 -maxdepth 1 | while read dir; do
|
||||
echocolor "[$APPNAME]" "listening to inotify events in $dir"
|
||||
inotifywait -r -m $dir | awk '$2 ~ /(CREATE|MODIFY|MOVED_TO|DELETE)/ { system("'$0' hook inotify_"$2" "$1""$3) }' &
|
||||
done
|
||||
}
|
||||
|
||||
|
|
@ -143,15 +118,14 @@ set_upload_path(){
|
|||
test -d $UPLOAD_PATH || mkdir $UPLOAD_PATH
|
||||
add_lib_to_db $UPLOAD_PATH
|
||||
id=$(sqlite3 $db "select id from libraries where path = '$UPLOAD_PATH';")
|
||||
set_global model_path_template "replace('--- \"{creator}/{modelId} \"\\n','\\n',char(10))"
|
||||
sqlite3 $db "INSERT INTO settings VALUES(6,'default_library',$id);
|
||||
INSERT INTO sqlite_sequence VALUES('settings',6);"
|
||||
sqlite3 $db "UPDATE settings set value = $id where var = 'default_library';"
|
||||
}
|
||||
|
||||
mount_dir(){
|
||||
find /mnt -type d -mindepth 1 -maxdepth 1 | grep -v janusweb | while read dir; do
|
||||
find /mnt -type d -mindepth 1 -maxdepth 1 | while read dir; do
|
||||
echocolor "[$APPNAME]" "mounting $dir as library"
|
||||
add_lib_to_db "$dir"
|
||||
ln -s "$dir" /usr/src/app/public/.
|
||||
done
|
||||
}
|
||||
|
||||
|
|
@ -178,30 +152,15 @@ mount_rclone(){
|
|||
test -n "$RCLONE_REMOTE" || libraries
|
||||
}
|
||||
|
||||
set_global(){
|
||||
echocolor "[$APPNAME]" "setting $1 to '$2'"
|
||||
debug sqlite3 /config/manyfold.sqlite3 'CREATE TABLE IF NOT EXISTS "settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "var" varchar NOT NULL, "value" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);'
|
||||
debug sqlite3 /config/manyfold.sqlite3 "
|
||||
INSERT OR REPLACE INTO settings (id, var, value, created_at, updated_at)
|
||||
VALUES (
|
||||
(SELECT id FROM settings WHERE var = '$1'),
|
||||
'$1',
|
||||
$2,
|
||||
COALESCE((SELECT created_at FROM settings WHERE var = '$1'), datetime('now')),
|
||||
datetime('now')
|
||||
);
|
||||
"
|
||||
set_theme(){
|
||||
test -z "$THEME" && return 0 # nothing to do
|
||||
echocolor "[$APPNAME]" "setting theme to '$THEME'"
|
||||
debug sqlite3 /config/manyfold.sqlite3 "UPDATE settings SET value = '"$THEME"' WHERE var == 'theme';"
|
||||
}
|
||||
|
||||
set_admin(){
|
||||
echocolor "[$APPNAME]" "adding xrforge admin"
|
||||
read -r -d '' QUERY <<EOF
|
||||
INSERT INTO users VALUES(1,'xrforge@localhost','\$2a\$12\$u/j8LRzbPiJRHmi1eV/fvOXXiKxN2vBGtNd.Pt28w.wOnq3rnfpzO','2025-07-25 10:52:56.989975','2025-07-25 12:46:27.338917','xrforge','{"models":true,"creators":true,"collections":true,"per_page":12}','{"grid_width":200,"grid_depth":200,"show_grid":true,"enable_pan_zoom":false,"background_colour":"#000000","object_colour":"#ffffff","render_style":"original"}','{"threshold":2,"heatmap":true,"keypair":true,"sorting":"frequency"}','{"missing":"danger","empty":"info","nesting":"warning","inefficient":"info","duplicate":"warning","no_image":"silent","no_3d_model":"silent","non_manifold":"warning","inside_out":"warning","no_license":"silent","no_links":"silent","no_creator":"silent","no_tags":"silent"}','{"hide_presupported_versions":true}',NULL,'2025-07-25 12:46:27.325287',NULL,NULL,0,NULL,NULL,NULL,NULL,'71863vkppj6k',1,1,1);
|
||||
EOF
|
||||
debug sqlite3 /config/manyfold.sqlite3 "$QUERY"
|
||||
debug sqlite3 /config/manyfold.sqlite3 'INSERT INTO users_roles VALUES(1,1);'
|
||||
#debug sqlite3 /config/manyfold.sqlite3 'INSERT INTO users_roles VALUES(1,4);'
|
||||
#debug sqlite3 /config/manyfold.sqlite3 "INSERT INTO sqlite_sequence VALUES('users',1);"
|
||||
set_modelpath(){
|
||||
echocolor "[$APPNAME]" "enforcing modelpath"
|
||||
debug sqlite3 /config/manyfold.sqlite3 "UPDATE settings SET value = replace('--- \"{creator}/{modelId}\"\n','\n',char(10)) WHERE var == 'model_path_template';"
|
||||
}
|
||||
|
||||
set_homepage(){
|
||||
|
|
@ -213,7 +172,8 @@ set_homepage(){
|
|||
rename_app(){
|
||||
echocolor "[$APPNAME]" "renaming manyfold to $APPNAME"
|
||||
sed -i 's/title: Manyfold/title: '$APPNAME'/g' /usr/src/app/config/locales/*.yml
|
||||
sed -i 's|powered_by_html:.*|powered_by_html: Radically opensource-powered by <a href="https://manifold.app" target="_blank">Manyfold</a>, <a href="https://xrfragment.org">XR Fragments</a>, <a href="https://github.com/jbaicoianu/janusweb" target="_blank">JanusWeb</a> and <a href="https://nixos.org" target="_blank">NIX</a>|g' /usr/src/app/config/locales/*.yml
|
||||
sed -i 's|powered_by_html:.*|powered_by_html: Radically opensource-powered by <a href="https://forgejo.isvery.ninja/coderofsalvation/xrforge">XR Forge</a>, <a href="https://manifold.app" target="_blank">Manyfold</a>, <a href="https://xrfragment.org">XR Fragments</a> and <a href="https://nixos.org" target="_blank">NIX</a>|g' /usr/src/app/config/locales/*.yml
|
||||
|
||||
sed -i 's|Models|Experiences|g' /usr/src/app/config/locales/*.yml /usr/src/app/config/locales/*/*.yml
|
||||
sed -i 's|Model|Experience|g' /usr/src/app/config/locales/*.yml /usr/src/app/config/locales/*/*.yml
|
||||
}
|
||||
|
|
@ -226,18 +186,10 @@ start_syslog(){
|
|||
}
|
||||
|
||||
scan_libraries(){
|
||||
sleep 10 # wait for manyfold/redis to boot first
|
||||
cd /usr/src/app
|
||||
echocolor "scanning libraries"
|
||||
BOOT_SCAN=$BOOT_SCAN flock -n /tmp/.scanlibrary bin/manyfold libraries scan
|
||||
rm /tmp/.scanlibrary
|
||||
}
|
||||
|
||||
scan_experience(){
|
||||
# don't do this when all libraries are already being scanned
|
||||
test -f /tmp/.scanlibrary && return 0
|
||||
id=$1
|
||||
cd /usr/src/app
|
||||
rails_query 'Model.find('$id').add_new_files_later(include_all_subfolders:false)'
|
||||
bin/manyfold libraries scan
|
||||
}
|
||||
|
||||
rails_query(){
|
||||
|
|
@ -251,81 +203,43 @@ force_public(){
|
|||
infinite 60 rails_query 'Model.find_each { |it| it.grant_permission_to("view", nil) }' &
|
||||
}
|
||||
|
||||
import_assets(){
|
||||
get_xrfragment_assets(){
|
||||
test -n "$NO_ASSETS" && return 0 # nothing to do here
|
||||
test -d /mnt/asset || {
|
||||
echocolor "fetching XR Fragments asset & templates"
|
||||
mkdir -p /mnt/asset/xrfragment /mnt/templates/xrfragment
|
||||
cd /tmp
|
||||
timeout 50 wget "https://codeberg.org/coderofsalvation/xrfragment/archive/main.zip"
|
||||
unzip main.zip
|
||||
cp -r xrfragment/assets/library /mnt/asset/xrfragment/\#1
|
||||
cp -r xrfragment/assets/template /mnt/templates/xrfragment/\#2
|
||||
}
|
||||
add_lib_to_db /mnt/asset
|
||||
add_lib_to_db /mnt/templates
|
||||
}
|
||||
|
||||
janusxr(){
|
||||
start_server(){
|
||||
while sleep 2s; do
|
||||
flock -n "$@"
|
||||
echocolor "[$1]" "'$2 $3 $4' exited (why?)...restarting"
|
||||
done
|
||||
}
|
||||
|
||||
cd /root/corsanywhere
|
||||
npm install
|
||||
PORT=5577 start_server ~/.run-corsanywhere node server.js &
|
||||
|
||||
cd /root
|
||||
chmod +x janus_server-linux
|
||||
PORT=5566 start_server ~/.run-janus-server ./janus_server-linux &
|
||||
|
||||
## we should do this in nix/docker.nix but the image gets into GB's :/
|
||||
#which git || apk add git
|
||||
#which janus || apk add janus-gateway
|
||||
#which node || apk add nodejs
|
||||
#which bash || apk add bash
|
||||
#cd /root
|
||||
|
||||
## install server
|
||||
#test -d janus-server || git clone --depth 1 https://github.com/janusvr/janus-server
|
||||
#cd janus-server
|
||||
#test -d node_modules || { apk add npm && npm install; }
|
||||
#test -f config.js || ln -f /root/.config/janus-server/config.js .
|
||||
#start_server(){
|
||||
# while sleep 2s; do
|
||||
# flock -n ~/.janus-server node server.js
|
||||
# echocolor "[janus-server]" "'node server.js' exited (why?)...restarting"
|
||||
# done
|
||||
#}
|
||||
#test -f ~/.janus-server || start_server &
|
||||
}
|
||||
|
||||
init_database(){
|
||||
test -f ${db}.xrforgeinit && exit 0 # already inited
|
||||
sleep 3
|
||||
test -z "$THEME" || set_global theme "'$THEME'"
|
||||
set_admin
|
||||
set_global site_name "'$APPNAME'"
|
||||
set_global site_tagline "'$TAGLINE'"
|
||||
set_global model_tags_auto_tag_new "replace('--- \"\"\\n','\\n',char(10))"
|
||||
set_global model_path_template "replace('--- \"{creator}/{modelId} \"\\n','\\n',char(10))"
|
||||
set_upload_path
|
||||
#set_global about "$ABOUT"
|
||||
import_assets
|
||||
mount_dir
|
||||
BOOT_SCAN=1 scan_libraries &
|
||||
touch ${db}.xrforgeinit
|
||||
}
|
||||
|
||||
# The new entrypoint of the docker
|
||||
boot(){
|
||||
echocolor "[$APPNAME]" "booting..."
|
||||
test -z "$NO_OVERLAYFS" && overlayfs
|
||||
test -z "$NO_DEFAULTDB" && db default
|
||||
start_syslog
|
||||
rename_app
|
||||
set_homepage
|
||||
set_theme
|
||||
set_modelpath
|
||||
mount_rclone
|
||||
set_upload_path
|
||||
force_public
|
||||
get_xrfragment_assets
|
||||
mount_dir
|
||||
start_hook_daemon
|
||||
mount_rclone
|
||||
janusxr
|
||||
cp /root/templates/ARhome/* /mnt/janusweb/.
|
||||
force_public &
|
||||
scan_libraries &
|
||||
hook boot # emit unixy hook-event (/root/hook.d/boot/* scripts)
|
||||
|
||||
# enable development mode (disables template caching etc)
|
||||
test -n "$DEV" && {
|
||||
sed -i 's|^exec.*|exec s6-setuidgid $PUID:$PGID bin/dev|g' /usr/src/app/bin/docker-entrypoint.sh
|
||||
sed -i 's|config.enable_reloading = false|config.enable_reloading = true|g' /usr/src/app/config/environments/production.rb
|
||||
apk add vim
|
||||
}
|
||||
|
|
|
|||
362
manyfold/manyfold.sql
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
PRAGMA foreign_keys=OFF;
|
||||
BEGIN TRANSACTION;
|
||||
CREATE TABLE IF NOT EXISTS "data_migrations" ("version" varchar NOT NULL PRIMARY KEY);
|
||||
INSERT INTO data_migrations VALUES('20221214230757');
|
||||
INSERT INTO data_migrations VALUES('20221220223040');
|
||||
INSERT INTO data_migrations VALUES('20230221174212');
|
||||
INSERT INTO data_migrations VALUES('20230308006000');
|
||||
INSERT INTO data_migrations VALUES('20230612080306');
|
||||
INSERT INTO data_migrations VALUES('20230613134254');
|
||||
INSERT INTO data_migrations VALUES('20230617222353');
|
||||
INSERT INTO data_migrations VALUES('20230628194944');
|
||||
INSERT INTO data_migrations VALUES('20240319155526');
|
||||
INSERT INTO data_migrations VALUES('20240322150022');
|
||||
INSERT INTO data_migrations VALUES('20240615085913');
|
||||
INSERT INTO data_migrations VALUES('20240731165647');
|
||||
INSERT INTO data_migrations VALUES('20240802094448');
|
||||
INSERT INTO data_migrations VALUES('20240805111500');
|
||||
INSERT INTO data_migrations VALUES('20240830121749');
|
||||
INSERT INTO data_migrations VALUES('20240830151650');
|
||||
INSERT INTO data_migrations VALUES('20240904152358');
|
||||
INSERT INTO data_migrations VALUES('20240909100000');
|
||||
INSERT INTO data_migrations VALUES('20240923114515');
|
||||
INSERT INTO data_migrations VALUES('20241013215000');
|
||||
INSERT INTO data_migrations VALUES('20241021125322');
|
||||
INSERT INTO data_migrations VALUES('20241118155027');
|
||||
INSERT INTO data_migrations VALUES('20250121164452');
|
||||
CREATE TABLE IF NOT EXISTS "schema_migrations" ("version" varchar NOT NULL PRIMARY KEY);
|
||||
INSERT INTO schema_migrations VALUES('20210130201037');
|
||||
INSERT INTO schema_migrations VALUES('20210130203243');
|
||||
INSERT INTO schema_migrations VALUES('20210205230918');
|
||||
INSERT INTO schema_migrations VALUES('20210210153744');
|
||||
INSERT INTO schema_migrations VALUES('20210212143001');
|
||||
INSERT INTO schema_migrations VALUES('20210212143002');
|
||||
INSERT INTO schema_migrations VALUES('20210212143003');
|
||||
INSERT INTO schema_migrations VALUES('20210212143004');
|
||||
INSERT INTO schema_migrations VALUES('20210212143005');
|
||||
INSERT INTO schema_migrations VALUES('20210212143006');
|
||||
INSERT INTO schema_migrations VALUES('20210220204055');
|
||||
INSERT INTO schema_migrations VALUES('20210225232756');
|
||||
INSERT INTO schema_migrations VALUES('20210225232841');
|
||||
INSERT INTO schema_migrations VALUES('20210301230017');
|
||||
INSERT INTO schema_migrations VALUES('20210302161202');
|
||||
INSERT INTO schema_migrations VALUES('20210314185822');
|
||||
INSERT INTO schema_migrations VALUES('20210318202638');
|
||||
INSERT INTO schema_migrations VALUES('20210321164508');
|
||||
INSERT INTO schema_migrations VALUES('20210330215825');
|
||||
INSERT INTO schema_migrations VALUES('20220105233138');
|
||||
INSERT INTO schema_migrations VALUES('20220106220519');
|
||||
INSERT INTO schema_migrations VALUES('20220612220115');
|
||||
INSERT INTO schema_migrations VALUES('20220612220116');
|
||||
INSERT INTO schema_migrations VALUES('20220612220117');
|
||||
INSERT INTO schema_migrations VALUES('20220614210023');
|
||||
INSERT INTO schema_migrations VALUES('20220614211256');
|
||||
INSERT INTO schema_migrations VALUES('20220614213902');
|
||||
INSERT INTO schema_migrations VALUES('20220617122809');
|
||||
INSERT INTO schema_migrations VALUES('20220619174307');
|
||||
INSERT INTO schema_migrations VALUES('20220626152444');
|
||||
INSERT INTO schema_migrations VALUES('20220702202932');
|
||||
INSERT INTO schema_migrations VALUES('20221128165903');
|
||||
INSERT INTO schema_migrations VALUES('20221210001132');
|
||||
INSERT INTO schema_migrations VALUES('20221219204414');
|
||||
INSERT INTO schema_migrations VALUES('20221220223340');
|
||||
INSERT INTO schema_migrations VALUES('20230202210000');
|
||||
INSERT INTO schema_migrations VALUES('20230202210001');
|
||||
INSERT INTO schema_migrations VALUES('20230203150000');
|
||||
INSERT INTO schema_migrations VALUES('20230221180921');
|
||||
INSERT INTO schema_migrations VALUES('20230222155910');
|
||||
INSERT INTO schema_migrations VALUES('20230303150029');
|
||||
INSERT INTO schema_migrations VALUES('20230305180823');
|
||||
INSERT INTO schema_migrations VALUES('20230307215826');
|
||||
INSERT INTO schema_migrations VALUES('20230308004237');
|
||||
INSERT INTO schema_migrations VALUES('20230308005021');
|
||||
INSERT INTO schema_migrations VALUES('20230313000000');
|
||||
INSERT INTO schema_migrations VALUES('20230313000001');
|
||||
INSERT INTO schema_migrations VALUES('20230316184012');
|
||||
INSERT INTO schema_migrations VALUES('20230324000000');
|
||||
INSERT INTO schema_migrations VALUES('20230615135601');
|
||||
INSERT INTO schema_migrations VALUES('20230628195018');
|
||||
INSERT INTO schema_migrations VALUES('20230707082403');
|
||||
INSERT INTO schema_migrations VALUES('20230710102250');
|
||||
INSERT INTO schema_migrations VALUES('20230711111009');
|
||||
INSERT INTO schema_migrations VALUES('20240122114207');
|
||||
INSERT INTO schema_migrations VALUES('20240127143358');
|
||||
INSERT INTO schema_migrations VALUES('20240131134832');
|
||||
INSERT INTO schema_migrations VALUES('20240208143319');
|
||||
INSERT INTO schema_migrations VALUES('20240209125409');
|
||||
INSERT INTO schema_migrations VALUES('20240228130246');
|
||||
INSERT INTO schema_migrations VALUES('20240306095646');
|
||||
INSERT INTO schema_migrations VALUES('20240319155251');
|
||||
INSERT INTO schema_migrations VALUES('20240319155903');
|
||||
INSERT INTO schema_migrations VALUES('20240322143621');
|
||||
INSERT INTO schema_migrations VALUES('20240410221112');
|
||||
INSERT INTO schema_migrations VALUES('20240410222922');
|
||||
INSERT INTO schema_migrations VALUES('20240418112821');
|
||||
INSERT INTO schema_migrations VALUES('20240423102250');
|
||||
INSERT INTO schema_migrations VALUES('20240610120318');
|
||||
INSERT INTO schema_migrations VALUES('20240614085913');
|
||||
INSERT INTO schema_migrations VALUES('20240701142651');
|
||||
INSERT INTO schema_migrations VALUES('20240703103707');
|
||||
INSERT INTO schema_migrations VALUES('20240703160732');
|
||||
INSERT INTO schema_migrations VALUES('20240727113215');
|
||||
INSERT INTO schema_migrations VALUES('20240727113216');
|
||||
INSERT INTO schema_migrations VALUES('20240727113217');
|
||||
INSERT INTO schema_migrations VALUES('20240731165646');
|
||||
INSERT INTO schema_migrations VALUES('20240827155630');
|
||||
INSERT INTO schema_migrations VALUES('20240904151944');
|
||||
INSERT INTO schema_migrations VALUES('20240924122004');
|
||||
INSERT INTO schema_migrations VALUES('20240926162407');
|
||||
INSERT INTO schema_migrations VALUES('20241004194445');
|
||||
INSERT INTO schema_migrations VALUES('20241007182824');
|
||||
INSERT INTO schema_migrations VALUES('20241009122540');
|
||||
INSERT INTO schema_migrations VALUES('20241015090803');
|
||||
INSERT INTO schema_migrations VALUES('20241017093301');
|
||||
INSERT INTO schema_migrations VALUES('20241017113112');
|
||||
INSERT INTO schema_migrations VALUES('20241021124608');
|
||||
INSERT INTO schema_migrations VALUES('20241025110218');
|
||||
INSERT INTO schema_migrations VALUES('20241105121830');
|
||||
INSERT INTO schema_migrations VALUES('20241122121621');
|
||||
INSERT INTO schema_migrations VALUES('20241128162213');
|
||||
INSERT INTO schema_migrations VALUES('20241128162214');
|
||||
INSERT INTO schema_migrations VALUES('20250114105808');
|
||||
INSERT INTO schema_migrations VALUES('20250121105010');
|
||||
INSERT INTO schema_migrations VALUES('20250122171731');
|
||||
INSERT INTO schema_migrations VALUES('20250222000000');
|
||||
INSERT INTO schema_migrations VALUES('20250305171530');
|
||||
INSERT INTO schema_migrations VALUES('20250407160721');
|
||||
INSERT INTO schema_migrations VALUES('20250407160722');
|
||||
INSERT INTO schema_migrations VALUES('20250408111644');
|
||||
INSERT INTO schema_migrations VALUES('20250408145956');
|
||||
INSERT INTO schema_migrations VALUES('20250409120155');
|
||||
INSERT INTO schema_migrations VALUES('20250409125753');
|
||||
INSERT INTO schema_migrations VALUES('20250423094525');
|
||||
INSERT INTO schema_migrations VALUES('20250425104619');
|
||||
INSERT INTO schema_migrations VALUES('20250520111046');
|
||||
INSERT INTO schema_migrations VALUES('20250609210440');
|
||||
INSERT INTO schema_migrations VALUES('20250620141805');
|
||||
INSERT INTO schema_migrations VALUES('20250621223410');
|
||||
INSERT INTO schema_migrations VALUES('20250629212656');
|
||||
INSERT INTO schema_migrations VALUES('20250716093106');
|
||||
INSERT INTO schema_migrations VALUES('20250724094951');
|
||||
INSERT INTO schema_migrations VALUES('20250806142734');
|
||||
CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
|
||||
INSERT INTO ar_internal_metadata VALUES('environment','production','2025-07-25 10:52:31.380052','2025-07-25 10:52:31.380054');
|
||||
CREATE TABLE IF NOT EXISTS "libraries" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "path" varchar NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "notes" varchar, "caption" varchar, "name" varchar, "tag_regex" text, "icon" text, "storage_service" varchar DEFAULT 'filesystem' NOT NULL, "s3_endpoint" varchar DEFAULT NULL, "s3_region" varchar DEFAULT NULL, "s3_bucket" varchar DEFAULT NULL, "s3_access_key_id" varchar DEFAULT NULL, "s3_secret_access_key" varchar DEFAULT NULL, "public_id" varchar, "s3_path_style" boolean DEFAULT 1 NOT NULL);
|
||||
INSERT INTO libraries VALUES(6,'/mnt/experiences','2025-08-14','2025-08-14','','','experiences',NULL,'','filesystem','','','','','','experiences',1);
|
||||
CREATE TABLE IF NOT EXISTS "tags" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "created_at" datetime, "updated_at" datetime, "taggings_count" integer DEFAULT 0);
|
||||
CREATE TABLE IF NOT EXISTS "taggings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "tag_id" integer, "taggable_type" varchar, "taggable_id" integer, "tagger_type" varchar, "tagger_id" integer, "context" varchar(128), "created_at" datetime, CONSTRAINT "fk_rails_9fcd2e236b"
|
||||
FOREIGN KEY ("tag_id")
|
||||
REFERENCES "tags" ("id")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "links" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "url" varchar, "linkable_type" varchar, "linkable_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "synced_at" datetime(6));
|
||||
INSERT INTO links VALUES(1,'https://xrfragment.org','Creator',1,'2025-08-14 12:57:19.685425','2025-08-14 12:57:19.685425',NULL);
|
||||
CREATE TABLE IF NOT EXISTS "settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "var" varchar NOT NULL, "value" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
|
||||
INSERT INTO settings VALUES(1,'default_library','6','2025-07-25 10:58:00.004576','2025-07-28 13:30:03.381376');
|
||||
INSERT INTO settings VALUES(2,'site_name',replace('--- XRForge\n','\n',char(10)),'2025-07-25 10:59:04.496016','2025-07-25 10:59:04.496016');
|
||||
INSERT INTO settings VALUES(3,'site_tagline',replace('--- Self-sovereign XR Experiences based on 3D files & URLs\n','\n',char(10)),'2025-07-25 10:59:04.519264','2025-07-25 10:59:04.519264');
|
||||
INSERT INTO settings VALUES(4,'theme','vapor','2025-07-25 10:59:04.522670','2025-07-28 13:47:54.690364');
|
||||
INSERT INTO settings VALUES(5,'about',replace('--- ''''\n','\n',char(10)),'2025-07-25 10:59:04.527612','2025-07-25 10:59:04.527612');
|
||||
INSERT INTO settings VALUES(6,'rules',replace('--- ''''\n','\n',char(10)),'2025-07-25 10:59:04.531378','2025-07-25 10:59:04.531378');
|
||||
INSERT INTO settings VALUES(7,'support_link',replace('--- https://forgejo.isvery.ninja/coderofsalvation/xrforge\n','\n',char(10)),'2025-07-25 10:59:04.533678','2025-07-25 10:59:04.533678');
|
||||
INSERT INTO settings VALUES(8,'site_icon',replace('--- ''''\n','\n',char(10)),'2025-07-25 10:59:04.536228','2025-07-25 14:19:06.192651');
|
||||
INSERT INTO settings VALUES(9,'model_path_template',replace('--- "{creator}/{modelId}"\n','\n',char(10)),'2025-07-28 15:57:18.598798','2025-07-28 15:57:18.598798');
|
||||
INSERT INTO settings VALUES(10,'parse_metadata_from_path',replace('--- true\n','\n',char(10)),'2025-07-28 15:57:18.624917','2025-07-28 15:57:18.624917');
|
||||
INSERT INTO settings VALUES(11,'safe_folder_names',replace('--- true\n','\n',char(10)),'2025-07-28 15:57:18.627224','2025-07-28 15:57:18.627224');
|
||||
INSERT INTO settings VALUES(12,'model_ignored_files',replace('---\n- !ruby/regexp /^\.[^\.]+/\n- !ruby/regexp /.*\/@eaDir\/.*/\n- !ruby/regexp /__MACOSX/\n','\n',char(10)),'2025-07-28 15:57:18.633182','2025-07-28 15:57:18.633182');
|
||||
INSERT INTO settings VALUES(13,'model_tags_filter_stop_words',replace('--- true\n','\n',char(10)),'2025-07-28 15:57:18.637858','2025-07-28 15:57:18.637858');
|
||||
INSERT INTO settings VALUES(14,'model_tags_tag_model_directory_name',replace('--- false\n','\n',char(10)),'2025-07-28 15:57:18.640351','2025-07-28 15:57:18.640351');
|
||||
INSERT INTO settings VALUES(15,'model_tags_stop_words_locale',replace('--- en\n','\n',char(10)),'2025-07-28 15:57:18.643185','2025-07-28 15:57:18.643185');
|
||||
INSERT INTO settings VALUES(16,'model_tags_custom_stop_words',replace('---\n- png\n- jpeg\n- jpg\n- jpe\n- pjpeg\n- gif\n- bmp\n- tiff\n- tif\n- svg\n- webp\n- threeds\n- 3ds\n- amf\n- ldr\n- mpd\n- scad\n- dwg\n- dxf\n- threemf\n- 3mf\n- gltf\n- glb\n- iges\n- igs\n- mtl\n- obj\n- step\n- stp\n- stl\n- collada\n- dae\n- draco\n- drc\n- vrml\n- wrl\n- abc\n- blend\n- brep\n- cheetah3d\n- jas\n- fbx\n- fcstd\n- f3d\n- f3z\n- ipt\n- iam\n- maya\n- ma\n- mb\n- mix\n- modo\n- lxo\n- ply\n- sketchup\n- skp\n- sldprt\n- hfp\n- speedtree\n- spm\n- x3d\n- gcode\n- bgcode\n- lychee\n- lys\n- lyt\n- chitubox\n- ctb\n- mpeg\n- mpg\n- mpe\n- webm\n- mp4\n- m4v\n- html\n- xhtml\n- text\n- txt\n- pdf\n- md\n- doc\n- docx\n- bin\n- gbr\n- gerber\n- geb\n- gb\n- gbrjob\n- drl\n- kicad_pro\n- pro\n- kicad_mod\n- kicad_pcb\n- kicad_sym\n- kicad_sch\n- sch\n- kicad_wks\n- zip\n- gzip\n- gz\n- rar\n- sevenz\n- 7z\n- bz2\n','\n',char(10)),'2025-07-28 15:57:18.645648','2025-07-28 15:57:18.645648');
|
||||
INSERT INTO settings VALUES(17,'model_tags_auto_tag_new',replace('--- "!new"\n','\n',char(10)),'2025-07-28 15:57:18.648518','2025-07-28 15:57:18.648518');
|
||||
CREATE TABLE IF NOT EXISTS "problems" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "problematic_type" varchar, "problematic_id" integer, "category" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "note" varchar DEFAULT NULL, "ignored" boolean DEFAULT 0 NOT NULL, "public_id" varchar, "in_progress" boolean DEFAULT 0 NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS "favorites" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "favoritable_type" varchar NOT NULL, "favoritable_id" integer NOT NULL, "favoritor_type" varchar NOT NULL, "favoritor_id" integer NOT NULL, "scope" varchar DEFAULT 'printed' NOT NULL, "blocked" boolean DEFAULT 0 NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS "flipper_features" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "key" varchar NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS "flipper_gates" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "feature_key" varchar NOT NULL, "key" varchar NOT NULL, "value" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS "roles" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "resource_type" varchar, "resource_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
|
||||
INSERT INTO roles VALUES(1,'administrator',NULL,NULL,'2025-07-25 10:52:35.066502','2025-07-25 10:52:35.066502');
|
||||
INSERT INTO roles VALUES(2,'moderator',NULL,NULL,'2025-07-25 10:52:35.079751','2025-07-25 10:52:35.079751');
|
||||
INSERT INTO roles VALUES(3,'contributor',NULL,NULL,'2025-07-25 10:52:35.081798','2025-07-25 10:52:35.081798');
|
||||
INSERT INTO roles VALUES(4,'member',NULL,NULL,'2025-07-25 10:52:35.083068','2025-07-25 10:52:35.083068');
|
||||
CREATE TABLE IF NOT EXISTS "users_roles" ("user_id" integer, "role_id" integer);
|
||||
INSERT INTO users_roles VALUES(1,4);
|
||||
INSERT INTO users_roles VALUES(1,1);
|
||||
CREATE TABLE IF NOT EXISTS "users" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "email" varchar DEFAULT '' NOT NULL, "encrypted_password" varchar DEFAULT '' NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "username" varchar NOT NULL, "pagination_settings" json DEFAULT '{"models":true,"creators":true,"collections":true,"per_page":12}', "renderer_settings" json DEFAULT '{"grid_width":200,"grid_depth":200,"show_grid":true,"enable_pan_zoom":false,"background_colour":"#000000","object_colour":"#ffffff","render_style":"original"}', "tag_cloud_settings" json DEFAULT '{"threshold":2,"heatmap":true,"keypair":true,"sorting":"frequency"}', "problem_settings" json DEFAULT '{"missing":"danger","empty":"info","nesting":"warning","inefficient":"info","duplicate":"warning","no_image":"silent","no_3d_model":"silent","non_manifold":"warning","inside_out":"warning","no_license":"silent","no_links":"silent","no_creator":"silent","no_tags":"silent"}', "file_list_settings" json DEFAULT '{"hide_presupported_versions":true}', "reset_password_token" varchar, "remember_created_at" datetime(6), "reset_password_sent_at" datetime(6), "interface_language" varchar, "failed_attempts" integer DEFAULT 0 NOT NULL, "locked_at" datetime(6), "auth_provider" varchar, "auth_uid" varchar, "sensitive_content_handling" varchar DEFAULT NULL, "public_id" varchar, "approved" boolean DEFAULT 1 NOT NULL, "quota" integer DEFAULT 1 NOT NULL, "quota_use_site_default" boolean DEFAULT 1 NOT NULL);
|
||||
INSERT INTO users VALUES(1,'xrforge@localhost','$2a$12$u/j8LRzbPiJRHmi1eV/fvOXXiKxN2vBGtNd.Pt28w.wOnq3rnfpzO','2025-07-25 10:52:56.989975','2025-07-25 12:46:27.338917','xrforge','{"models":true,"creators":true,"collections":true,"per_page":12}','{"grid_width":200,"grid_depth":200,"show_grid":true,"enable_pan_zoom":false,"background_colour":"#000000","object_colour":"#ffffff","render_style":"original"}','{"threshold":2,"heatmap":true,"keypair":true,"sorting":"frequency"}','{"missing":"danger","empty":"info","nesting":"warning","inefficient":"info","duplicate":"warning","no_image":"silent","no_3d_model":"silent","non_manifold":"warning","inside_out":"warning","no_license":"silent","no_links":"silent","no_creator":"silent","no_tags":"silent"}','{"hide_presupported_versions":true}',NULL,'2025-07-25 12:46:27.325287',NULL,NULL,0,NULL,NULL,NULL,NULL,'71863vkppj6k',1,1,1);
|
||||
CREATE TABLE IF NOT EXISTS "federails_followings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "actor_id" integer NOT NULL, "target_actor_id" integer NOT NULL, "status" integer DEFAULT 0, "federated_url" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "uuid" varchar DEFAULT NULL, CONSTRAINT "fk_rails_2e62338faa"
|
||||
FOREIGN KEY ("actor_id")
|
||||
REFERENCES "federails_actors" ("id")
|
||||
, CONSTRAINT "fk_rails_4a2870c181"
|
||||
FOREIGN KEY ("target_actor_id")
|
||||
REFERENCES "federails_actors" ("id")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "federails_activities" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "entity_type" varchar NOT NULL, "entity_id" integer NOT NULL, "action" varchar NOT NULL, "actor_id" integer NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "uuid" varchar DEFAULT NULL, CONSTRAINT "fk_rails_85ef6259df"
|
||||
FOREIGN KEY ("actor_id")
|
||||
REFERENCES "federails_actors" ("id")
|
||||
);
|
||||
INSERT INTO federails_activities VALUES(1,'Federails::Actor',2,'Create',1,'2025-08-14 12:57:19.593979','2025-08-14 12:57:19.737880','22e24c27-09ac-4f61-8897-1e176f16fcf7');
|
||||
CREATE TABLE IF NOT EXISTS "caber_relations" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "subject_type" varchar, "subject_id" integer, "permission" varchar, "object_type" varchar NOT NULL, "object_id" integer NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
|
||||
INSERT INTO caber_relations VALUES(1,'User',1,'own','Creator',1,'2025-08-14 12:57:19.679313','2025-08-14 12:57:19.679313');
|
||||
INSERT INTO caber_relations VALUES(2,'Role',4,'view','Creator',1,'2025-08-14 12:57:19.762509','2025-08-14 12:57:19.762509');
|
||||
CREATE TABLE IF NOT EXISTS "federails_moderation_reports" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "federated_url" varchar, "federails_actor_id" integer, "content" varchar, "object_type" varchar, "object_id" integer, "resolved_at" datetime(6), "resolution" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_a5cda24d4c"
|
||||
FOREIGN KEY ("federails_actor_id")
|
||||
REFERENCES "federails_actors" ("id")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "federails_moderation_domain_blocks" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "domain" varchar NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS "comments" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "public_id" varchar NOT NULL, "commenter_type" varchar, "commenter_id" integer, "commentable_type" varchar NOT NULL, "commentable_id" integer NOT NULL, "comment" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "system" boolean DEFAULT 0 NOT NULL, "sensitive" boolean DEFAULT 0 NOT NULL, "federated_url" varchar, "federails_actor_id" integer, CONSTRAINT "fk_rails_3a181ceff0"
|
||||
FOREIGN KEY ("federails_actor_id")
|
||||
REFERENCES "federails_actors" ("id")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "oauth_applications" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "uid" varchar NOT NULL, "secret" varchar NOT NULL, "redirect_uri" text, "scopes" varchar DEFAULT '' NOT NULL, "confidential" boolean DEFAULT 1 NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "owner_id" bigint, "owner_type" varchar);
|
||||
CREATE TABLE IF NOT EXISTS "oauth_access_grants" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "resource_owner_id" integer NOT NULL, "application_id" integer NOT NULL, "token" varchar NOT NULL, "expires_in" integer NOT NULL, "redirect_uri" text NOT NULL, "scopes" varchar DEFAULT '' NOT NULL, "created_at" datetime(6) NOT NULL, "revoked_at" datetime(6), CONSTRAINT "fk_rails_b4b53e07b8"
|
||||
FOREIGN KEY ("application_id")
|
||||
REFERENCES "oauth_applications" ("id")
|
||||
, CONSTRAINT "fk_rails_330c32d8d9"
|
||||
FOREIGN KEY ("resource_owner_id")
|
||||
REFERENCES "users" ("id")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "oauth_access_tokens" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "resource_owner_id" integer, "application_id" integer NOT NULL, "token" varchar NOT NULL, "refresh_token" varchar, "expires_in" integer, "scopes" varchar, "created_at" datetime(6) NOT NULL, "revoked_at" datetime(6), "previous_refresh_token" varchar DEFAULT '' NOT NULL, CONSTRAINT "fk_rails_732cb83ab7"
|
||||
FOREIGN KEY ("application_id")
|
||||
REFERENCES "oauth_applications" ("id")
|
||||
, CONSTRAINT "fk_rails_ee63f25419"
|
||||
FOREIGN KEY ("resource_owner_id")
|
||||
REFERENCES "users" ("id")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "federails_actors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "federated_url" varchar, "username" varchar, "server" varchar, "inbox_url" varchar, "outbox_url" varchar, "followers_url" varchar, "followings_url" varchar, "profile_url" varchar, "entity_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "entity_type" varchar DEFAULT NULL, "public_key" text, "private_key" text, "uuid" varchar, "extensions" json, "local" boolean DEFAULT 0 NOT NULL, "actor_type" varchar, "tombstoned_at" datetime(6));
|
||||
INSERT INTO federails_actors VALUES(1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,'2025-07-25 10:52:57.029315','2025-07-25 10:52:57.029315','User',NULL,NULL,'eb64d114-1bc7-4cb3-8be6-350d23ccfb3e',NULL,1,NULL,NULL);
|
||||
INSERT INTO federails_actors VALUES(2,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,'2025-08-14 12:57:19.669133','2025-08-14 12:57:19.669133','Creator',NULL,NULL,'a544a58c-52be-4013-9415-440a9014b4c8',NULL,1,NULL,NULL);
|
||||
CREATE TABLE IF NOT EXISTS "model_files" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "filename" varchar, "model_id" integer NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "presupported" boolean DEFAULT 0 NOT NULL, "y_up" boolean DEFAULT 0 NOT NULL, "digest" varchar, "notes" text, "caption" text, "size" bigint, "presupported_version_id" integer, "attachment_data" json, "public_id" varchar, "filename_lower" varchar GENERATED ALWAYS AS (LOWER(filename)) STORED, "previewable" boolean DEFAULT 0 NOT NULL, CONSTRAINT "fk_rails_b5ac05b6e3"
|
||||
FOREIGN KEY ("presupported_version_id")
|
||||
REFERENCES "model_files" ("id")
|
||||
, CONSTRAINT "fk_rails_8e378ff647"
|
||||
FOREIGN KEY ("model_id")
|
||||
REFERENCES "models" ("id")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "altcha_solutions" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "algorithm" varchar, "challenge" varchar, "salt" varchar, "signature" varchar, "number" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS "models" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "path" varchar NOT NULL, "library_id" integer NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "preview_file_id" integer, "creator_id" integer, "notes" text, "caption" text, "collection_id" integer, "slug" varchar, "license" varchar, "public_id" varchar, "name_lower" varchar GENERATED ALWAYS AS (LOWER(name)) STORED, "sensitive" boolean DEFAULT 0 NOT NULL, "indexable" varchar, "ai_indexable" varchar, CONSTRAINT "fk_rails_cdf016e15c"
|
||||
FOREIGN KEY ("collection_id")
|
||||
REFERENCES "collections" ("id")
|
||||
, CONSTRAINT "fk_rails_aaa717f5bb"
|
||||
FOREIGN KEY ("library_id")
|
||||
REFERENCES "libraries" ("id")
|
||||
, CONSTRAINT "fk_rails_3b8b50d3f3"
|
||||
FOREIGN KEY ("creator_id")
|
||||
REFERENCES "creators" ("id")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "creators" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "notes" text, "caption" text, "slug" varchar, "public_id" varchar, "name_lower" varchar GENERATED ALWAYS AS (LOWER(name)) STORED, "indexable" varchar, "ai_indexable" varchar);
|
||||
INSERT INTO creators VALUES(1,'xrfragments','2025-08-14 12:57:19.593979','2025-08-14 12:57:19.593979','XR Fragments is an open specification for hyperlinking & deeplinking 3D fileformats .\nTurn 3D files into linkable AR/VR websites .\n3D files with XR Fragments enable interoperable, networkable and interactions via so-called extras and promote URL standards','deeplinking and adressing 3D objects using URI''s','xrfragments','bdb7rrdjwtdm','yes',NULL);
|
||||
CREATE TABLE IF NOT EXISTS "collections" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "notes" text, "caption" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "collection_id" integer, "slug" varchar, "public_id" varchar, "name_lower" varchar GENERATED ALWAYS AS (LOWER(name)) STORED, "creator_id" integer, "indexable" varchar, "ai_indexable" varchar, CONSTRAINT "fk_rails_63724415e9"
|
||||
FOREIGN KEY ("collection_id")
|
||||
REFERENCES "collections" ("id")
|
||||
, CONSTRAINT "fk_rails_ab2fec83b3"
|
||||
FOREIGN KEY ("creator_id")
|
||||
REFERENCES "creators" ("id")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "fasp_client_providers" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "uuid" varchar, "name" varchar, "base_url" varchar, "server_id" varchar, "public_key" varchar, "ed25519_signing_key" varchar, "status" integer, "capabilities" json, "privacy_policy" json, "sign_in_url" varchar, "contact_email" varchar, "fediverse_account" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
|
||||
INSERT INTO sqlite_sequence VALUES('taggings',0);
|
||||
INSERT INTO sqlite_sequence VALUES('users',1);
|
||||
INSERT INTO sqlite_sequence VALUES('comments',0);
|
||||
INSERT INTO sqlite_sequence VALUES('oauth_access_grants',0);
|
||||
INSERT INTO sqlite_sequence VALUES('oauth_access_tokens',0);
|
||||
INSERT INTO sqlite_sequence VALUES('federails_actors',2);
|
||||
INSERT INTO sqlite_sequence VALUES('model_files',0);
|
||||
INSERT INTO sqlite_sequence VALUES('models',0);
|
||||
INSERT INTO sqlite_sequence VALUES('creators',1);
|
||||
INSERT INTO sqlite_sequence VALUES('collections',0);
|
||||
INSERT INTO sqlite_sequence VALUES('roles',4);
|
||||
INSERT INTO sqlite_sequence VALUES('libraries',6);
|
||||
INSERT INTO sqlite_sequence VALUES('settings',17);
|
||||
INSERT INTO sqlite_sequence VALUES('caber_relations',2);
|
||||
INSERT INTO sqlite_sequence VALUES('links',1);
|
||||
INSERT INTO sqlite_sequence VALUES('federails_activities',1);
|
||||
CREATE UNIQUE INDEX "index_tags_on_name" ON "tags" ("name");
|
||||
CREATE UNIQUE INDEX "taggings_idx" ON "taggings" ("tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type");
|
||||
CREATE INDEX "taggings_taggable_context_idx" ON "taggings" ("taggable_id", "taggable_type", "context");
|
||||
CREATE INDEX "index_taggings_on_taggable_type" ON "taggings" ("taggable_type");
|
||||
CREATE INDEX "index_taggings_on_context" ON "taggings" ("context");
|
||||
CREATE INDEX "index_taggings_on_tagger_id_and_tagger_type" ON "taggings" ("tagger_id", "tagger_type");
|
||||
CREATE INDEX "taggings_idy" ON "taggings" ("taggable_id", "taggable_type", "tagger_id", "context");
|
||||
CREATE INDEX "index_links_on_linkable" ON "links" ("linkable_type", "linkable_id");
|
||||
CREATE UNIQUE INDEX "index_settings_on_var" ON "settings" ("var");
|
||||
CREATE INDEX "index_problems_on_problematic" ON "problems" ("problematic_type", "problematic_id");
|
||||
CREATE INDEX "index_favorites_on_favoritor" ON "favorites" ("favoritor_type", "favoritor_id");
|
||||
CREATE INDEX "index_favorites_on_scope" ON "favorites" ("scope");
|
||||
CREATE INDEX "index_favorites_on_blocked" ON "favorites" ("blocked");
|
||||
CREATE INDEX "fk_favorites" ON "favorites" ("favoritor_id", "favoritor_type");
|
||||
CREATE INDEX "fk_favoritables" ON "favorites" ("favoritable_id", "favoritable_type");
|
||||
CREATE UNIQUE INDEX "uniq_favorites__and_favoritables" ON "favorites" ("favoritable_type", "favoritable_id", "favoritor_type", "favoritor_id", "scope");
|
||||
CREATE UNIQUE INDEX "index_problems_on_category_and_problematic_id_and_type" ON "problems" ("category", "problematic_id", "problematic_type");
|
||||
CREATE UNIQUE INDEX "index_flipper_features_on_key" ON "flipper_features" ("key");
|
||||
CREATE UNIQUE INDEX "index_flipper_gates_on_feature_key_and_key_and_value" ON "flipper_gates" ("feature_key", "key", "value");
|
||||
CREATE INDEX "index_roles_on_resource" ON "roles" ("resource_type", "resource_id");
|
||||
CREATE INDEX "index_users_roles_on_role_id" ON "users_roles" ("role_id");
|
||||
CREATE INDEX "index_roles_on_name_and_resource_type_and_resource_id" ON "roles" ("name", "resource_type", "resource_id");
|
||||
CREATE INDEX "index_users_roles_on_user_id_and_role_id" ON "users_roles" ("user_id", "role_id");
|
||||
CREATE UNIQUE INDEX "index_users_on_email" ON "users" ("email");
|
||||
CREATE UNIQUE INDEX "index_users_on_username" ON "users" ("username");
|
||||
CREATE UNIQUE INDEX "index_users_on_reset_password_token" ON "users" ("reset_password_token");
|
||||
CREATE INDEX "index_federails_followings_on_target_actor_id" ON "federails_followings" ("target_actor_id");
|
||||
CREATE UNIQUE INDEX "index_federails_followings_on_actor_id_and_target_actor_id" ON "federails_followings" ("actor_id", "target_actor_id");
|
||||
CREATE INDEX "index_federails_activities_on_entity" ON "federails_activities" ("entity_type", "entity_id");
|
||||
CREATE INDEX "index_federails_activities_on_actor_id" ON "federails_activities" ("actor_id");
|
||||
CREATE INDEX "index_caber_relations_on_subject" ON "caber_relations" ("subject_type", "subject_id");
|
||||
CREATE INDEX "index_caber_relations_on_object" ON "caber_relations" ("object_type", "object_id");
|
||||
CREATE UNIQUE INDEX "idx_on_subject_id_subject_type_object_id_object_typ_a279b094be" ON "caber_relations" ("subject_id", "subject_type", "object_id", "object_type");
|
||||
CREATE INDEX "index_problems_on_public_id" ON "problems" ("public_id");
|
||||
CREATE INDEX "index_libraries_on_public_id" ON "libraries" ("public_id");
|
||||
CREATE UNIQUE INDEX "index_federails_activities_on_uuid" ON "federails_activities" ("uuid");
|
||||
CREATE UNIQUE INDEX "index_federails_followings_on_uuid" ON "federails_followings" ("uuid");
|
||||
CREATE INDEX "index_users_on_public_id" ON "users" ("public_id");
|
||||
CREATE INDEX "index_users_on_approved" ON "users" ("approved");
|
||||
CREATE INDEX "index_federails_moderation_reports_on_federails_actor_id" ON "federails_moderation_reports" ("federails_actor_id");
|
||||
CREATE INDEX "index_federails_moderation_reports_on_object" ON "federails_moderation_reports" ("object_type", "object_id");
|
||||
CREATE UNIQUE INDEX "index_federails_moderation_domain_blocks_on_domain" ON "federails_moderation_domain_blocks" ("domain");
|
||||
CREATE UNIQUE INDEX "index_comments_on_public_id" ON "comments" ("public_id");
|
||||
CREATE INDEX "index_comments_on_commenter" ON "comments" ("commenter_type", "commenter_id");
|
||||
CREATE INDEX "index_comments_on_commentable" ON "comments" ("commentable_type", "commentable_id");
|
||||
CREATE INDEX "index_comments_on_federails_actor_id" ON "comments" ("federails_actor_id");
|
||||
CREATE UNIQUE INDEX "index_oauth_applications_on_uid" ON "oauth_applications" ("uid");
|
||||
CREATE INDEX "index_oauth_access_grants_on_resource_owner_id" ON "oauth_access_grants" ("resource_owner_id");
|
||||
CREATE INDEX "index_oauth_access_grants_on_application_id" ON "oauth_access_grants" ("application_id");
|
||||
CREATE UNIQUE INDEX "index_oauth_access_grants_on_token" ON "oauth_access_grants" ("token");
|
||||
CREATE INDEX "index_oauth_access_tokens_on_resource_owner_id" ON "oauth_access_tokens" ("resource_owner_id");
|
||||
CREATE INDEX "index_oauth_access_tokens_on_application_id" ON "oauth_access_tokens" ("application_id");
|
||||
CREATE UNIQUE INDEX "index_oauth_access_tokens_on_token" ON "oauth_access_tokens" ("token");
|
||||
CREATE UNIQUE INDEX "index_oauth_access_tokens_on_refresh_token" ON "oauth_access_tokens" ("refresh_token");
|
||||
CREATE INDEX "index_oauth_applications_on_owner_id_and_owner_type" ON "oauth_applications" ("owner_id", "owner_type");
|
||||
CREATE UNIQUE INDEX "index_federails_actors_on_federated_url" ON "federails_actors" ("federated_url");
|
||||
CREATE UNIQUE INDEX "index_federails_actors_on_entity" ON "federails_actors" ("entity_type", "entity_id");
|
||||
CREATE UNIQUE INDEX "index_federails_actors_on_uuid" ON "federails_actors" ("uuid");
|
||||
CREATE INDEX "index_model_files_on_model_id" ON "model_files" ("model_id");
|
||||
CREATE INDEX "index_model_files_on_digest" ON "model_files" ("digest");
|
||||
CREATE INDEX "index_model_files_on_presupported_version_id" ON "model_files" ("presupported_version_id");
|
||||
CREATE UNIQUE INDEX "index_model_files_on_filename_and_model_id" ON "model_files" ("filename", "model_id");
|
||||
CREATE INDEX "index_model_files_on_public_id" ON "model_files" ("public_id");
|
||||
CREATE INDEX "index_model_files_on_filename_lower" ON "model_files" ("filename_lower");
|
||||
CREATE INDEX "index_links_on_linkable_id_and_linkable_type_and_url" ON "links" ("linkable_id", "linkable_type", "url");
|
||||
CREATE UNIQUE INDEX "index_altcha_solutions" ON "altcha_solutions" ("algorithm", "challenge", "salt", "signature", "number");
|
||||
CREATE INDEX "index_models_on_library_id" ON "models" ("library_id");
|
||||
CREATE INDEX "index_models_on_creator_id" ON "models" ("creator_id");
|
||||
CREATE INDEX "index_models_on_preview_file_id" ON "models" ("preview_file_id");
|
||||
CREATE INDEX "index_models_on_collection_id" ON "models" ("collection_id");
|
||||
CREATE INDEX "index_models_on_slug" ON "models" ("slug");
|
||||
CREATE UNIQUE INDEX "index_models_on_path_and_library_id" ON "models" ("path", "library_id");
|
||||
CREATE INDEX "index_models_on_public_id" ON "models" ("public_id");
|
||||
CREATE INDEX "index_models_on_name_lower" ON "models" ("name_lower");
|
||||
CREATE UNIQUE INDEX "index_creators_on_name" ON "creators" ("name");
|
||||
CREATE UNIQUE INDEX "index_creators_on_slug" ON "creators" ("slug");
|
||||
CREATE INDEX "index_creators_on_public_id" ON "creators" ("public_id");
|
||||
CREATE INDEX "index_creators_on_name_lower" ON "creators" ("name_lower");
|
||||
CREATE INDEX "index_collections_on_collection_id" ON "collections" ("collection_id");
|
||||
CREATE UNIQUE INDEX "index_collections_on_name" ON "collections" ("name");
|
||||
CREATE UNIQUE INDEX "index_collections_on_slug" ON "collections" ("slug");
|
||||
CREATE INDEX "index_collections_on_public_id" ON "collections" ("public_id");
|
||||
CREATE INDEX "index_collections_on_name_lower" ON "collections" ("name_lower");
|
||||
CREATE INDEX "index_collections_on_creator_id" ON "collections" ("creator_id");
|
||||
CREATE INDEX "index_models_on_created_at" ON "models" ("created_at");
|
||||
CREATE INDEX "index_models_on_updated_at" ON "models" ("updated_at");
|
||||
CREATE INDEX "index_creators_on_created_at" ON "creators" ("created_at");
|
||||
CREATE INDEX "index_creators_on_updated_at" ON "creators" ("updated_at");
|
||||
CREATE INDEX "index_collections_on_created_at" ON "collections" ("created_at");
|
||||
CREATE INDEX "index_collections_on_updated_at" ON "collections" ("updated_at");
|
||||
CREATE INDEX "index_links_on_url" ON "links" ("url");
|
||||
COMMIT;
|
||||
|
|
@ -1 +0,0 @@
|
|||
<janus-ui-settings-panels></janus-ui-settings-panels>
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"apps": {
|
||||
"inventory": "./apps/inventory/inventory.json",
|
||||
"editor": "./apps/editor/editor.json",
|
||||
"locomotion": "./apps/locomotion/locomotion.json",
|
||||
"virtualgamepad": "./apps/virtualgamepad/virtualgamepad.json",
|
||||
"buttons": "./apps/buttons/buttons.json",
|
||||
"xrmenu": "./apps/xrmenu/xrmenu.json"
|
||||
},
|
||||
"includes": [
|
||||
],
|
||||
"templates": {
|
||||
"janusweb.ui": "./preview.html"
|
||||
},
|
||||
"css": [
|
||||
"./themes/preview.css"
|
||||
],
|
||||
"scripts": [
|
||||
]
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
#!/bin/sh
|
||||
test -z "$AFRAME_VERSION" && exit 0 # nothing to do
|
||||
mkdir /usr/src/app/public/aframe || true
|
||||
wget "https://aframe.io/releases/${AFRAME_VERSION}/aframe.min.js" -O /usr/src/app/public/aframe/aframe.min.js
|
||||
|
||||
test -f /usr/src/app/public/aframe/index.html || echo '<html>
|
||||
<head>
|
||||
<script src="/aframe/aframe.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a-scene>
|
||||
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
|
||||
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
|
||||
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
|
||||
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
|
||||
<a-sky color="#ECECEC"></a-sky>
|
||||
</a-scene>
|
||||
</body>
|
||||
</html>
|
||||
' > /usr/src/app/public/aframe/index.html
|
||||
|
|
@ -2,5 +2,5 @@
|
|||
test -z "$GODOT_VERSION" && exit 0 # nothing to do
|
||||
wget "https://github.com/godotengine/godot/releases/download/${GODOT_VERSION}/Godot_v${GODOT_VERSION}_web_editor.zip" -O /root/godot.zip
|
||||
cd /usr/src/app/public/godot
|
||||
unzip -o /root/godot.zip
|
||||
unzip /root/godot.zip
|
||||
cp index.override.html index.html # make index.html autoload a manyfold zip-file (passed via HTTP query)
|
||||
|
|
|
|||
|
|
@ -17,5 +17,5 @@ test -n "$FEDERATE_DRIVE_CERT" && test -m "$FEDERATE_DRIVE_KEY" && {
|
|||
|
||||
set -x
|
||||
rclone serve http \
|
||||
--links --exclude .xrforge --poll-interval $FEDERATE_DRIVE_CACHE \
|
||||
--poll-interval $FEDERATE_DRIVE_CACHE \
|
||||
--addr 0.0.0.0:$FEDERATE_DRIVE_PORT ${AUTH} ${SSL} $FEDERATE_DRIVE_PATH &> /var/log/rclone.log &
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/sh
|
||||
/manyfold/cli/manyfold.sh init_database
|
||||
|
|
@ -1,3 +1,13 @@
|
|||
#!/bin/sh
|
||||
test -n "$RUNTESTS" || exit 0
|
||||
exec /test/runtests.sh
|
||||
|
||||
test -z "$RUNTESTS" && exit 0 # nothing to do
|
||||
|
||||
echo ""
|
||||
echo "[!] RUNTESTS=1 was set "
|
||||
echo "[.] running tests in /test/*"
|
||||
echo ""
|
||||
|
||||
find -L /test/* -type f -executable -maxdepth 1 | while read testscript; do
|
||||
echo "[.] test: "$testscript
|
||||
$testscript "$@" 2>&1 | awk '{ print " | "$0 }'
|
||||
done
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
#!/bin/sh
|
||||
# remove succesful tasks
|
||||
ts | awk '$4 == 0 { print $1 }' | xargs -n1 ts -r
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
#!/bin/sh
|
||||
dir="$(dirname $1)/.xrforge"
|
||||
mkdir -p "$dir" || true
|
||||
cd "$dir"
|
||||
echo "[v] reset log.txt"
|
||||
date > log.txt
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'json'
|
||||
require_relative './../../xrforge.rb'
|
||||
|
||||
# Check if a filename is provided
|
||||
if ARGV.length != 1
|
||||
puts "Usage: #{$0} <path/to/experience/somefile.xxx>"
|
||||
exit 1
|
||||
end
|
||||
|
||||
filename = ARGV[0]
|
||||
|
||||
require 'base64'
|
||||
require 'json'
|
||||
|
||||
begin
|
||||
|
||||
# Change the directory
|
||||
dir = File.dirname(filename)
|
||||
Dir.chdir( dir )
|
||||
# Read and parse the JSON file
|
||||
data = JSON.parse( File.read( "datapackage.json" ) )
|
||||
logfile = File.join( File.dirname(filename), ".xrforge/log.txt" )
|
||||
|
||||
|
||||
data['resources'].each do |resource|
|
||||
ext = File.extname(resource['path'])
|
||||
filenameWithoutExt = File.basename(resource['path'], ext)
|
||||
if ! XRForge::MODEL_EXT.any?( ext )
|
||||
next # skip unknown 3d files
|
||||
end
|
||||
if ! resource['path'].match(/^[a-zA-Z0-9]/)
|
||||
next # skip filenames like '_generated.glb' e.g. (produced by hooks)
|
||||
end
|
||||
|
||||
XRForge.log("✅ unpacking textures", logfile)
|
||||
gltf_path = ".xrforge/#{filenameWithoutExt}.gltf"
|
||||
system("assimp export #{resource['path']} #{gltf_path} --embed-textures")
|
||||
Dir.chdir("#{dir}/.xrforge")
|
||||
system("assimp extract #{filenameWithoutExt}.gltf | sed 's|/.*/||g'")
|
||||
Dir.chdir(dir)
|
||||
|
||||
# Read and parse the GLTF JSON
|
||||
gltf = JSON.parse(File.read(gltf_path))
|
||||
|
||||
# Ensure images section exists
|
||||
unless gltf['images'] && gltf['images'].is_a?(Array)
|
||||
puts "✅ No 'images' array found in #{gltf_path}"
|
||||
next
|
||||
end
|
||||
|
||||
# Iterate through the images array
|
||||
gltf['images'].each_with_index do |img, i|
|
||||
imgExts = ["jpg","png"]
|
||||
new_filename = "#{dir}/#{filenameWithoutExt}_img#{i}"
|
||||
old_filename = "#{dir}/.xrforge/#{filenameWithoutExt}_img#{i}"
|
||||
|
||||
imgExts.each do |imgExt|
|
||||
# move file to modeldirectory (but dont overwrite if user overwrite it)
|
||||
if File.exist?("#{old_filename}.#{imgExt}") && !File.exist?("#{new_filename}.#{imgExt}")
|
||||
puts "✅ Renaming #{old_filename}.#{imgExt} -> #{new_filename}.#{imgExt}"
|
||||
File.rename( "#{old_filename}.#{imgExt}", "#{new_filename}.#{imgExt}" )
|
||||
else
|
||||
if File.exist?("#{new_filename}.#{imgExt}")
|
||||
puts "✅ Not overwriting (useruploaded) #{new_filename}.#{imgExt}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
XRForge.log(" ", logfile)
|
||||
|
||||
rescue Errno::ENOENT
|
||||
puts "File #{filename} not found"
|
||||
rescue JSON::ParserError
|
||||
puts "Error parsing JSON from #{filename}"
|
||||
rescue => e
|
||||
puts "An error occurred: #{e.message}"
|
||||
end
|
||||
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'json'
|
||||
require_relative './../../xrforge.rb'
|
||||
|
||||
# Check if a filename is provided
|
||||
if ARGV.length != 1
|
||||
puts "Usage: #{$0} <path/to/experience/somefile.xxx>"
|
||||
exit 1
|
||||
end
|
||||
|
||||
filename = ARGV[0]
|
||||
|
||||
require 'base64'
|
||||
require 'json'
|
||||
|
||||
begin
|
||||
|
||||
# Change the directory
|
||||
dir = File.dirname(filename)
|
||||
Dir.chdir( dir )
|
||||
# Read and parse the JSON file
|
||||
data = JSON.parse( File.read( "datapackage.json" ) )
|
||||
logfile = File.join( File.dirname(filename), ".xrforge/log.txt" )
|
||||
update = false
|
||||
|
||||
data['resources'].each do |resource|
|
||||
ext = File.extname(resource['path'])
|
||||
filenameWithoutExt = File.basename(resource['path'], ext)
|
||||
if ! XRForge::MODEL_EXT.any?( ext )
|
||||
next # skip unknown 3d files
|
||||
end
|
||||
if ! resource['path'].match(/^[a-zA-Z0-9]/)
|
||||
next # skip filenames like '_generated.glb' e.g. (produced by hooks)
|
||||
end
|
||||
|
||||
XRForge.log("✅ compiling textures", logfile)
|
||||
gltf_path = ".xrforge/#{filenameWithoutExt}.gltf"
|
||||
|
||||
# Read and parse the GLTF JSON
|
||||
gltf = JSON.parse(File.read(gltf_path))
|
||||
|
||||
# Ensure images section exists
|
||||
unless gltf['images'] && gltf['images'].is_a?(Array)
|
||||
XRForge.log("✅ No 'images' array found in #{gltf_path}",logfile)
|
||||
next
|
||||
end
|
||||
|
||||
# Iterate through the images array
|
||||
gltf['images'].each_with_index do |img, i|
|
||||
|
||||
# move file to modeldirectory (but dont overwrite if user overwrite it)
|
||||
imgExts = ["jpg","png"]
|
||||
imgExts.each do |imgExt|
|
||||
new_filename = "#{dir}/#{filenameWithoutExt}_img#{i}.#{imgExt}"
|
||||
if File.exist?(new_filename)
|
||||
XRForge.log("🤔 detected #{File.basename(new_filename)}",logfile)
|
||||
XRForge.log("✅ importing #{File.basename(new_filename)} -> #{resource['path']}",logfile)
|
||||
img['uri'] = new_filename # NOTE: editing uri will cause assimp to drop image['name'] when exporting :/
|
||||
update = true
|
||||
end
|
||||
end
|
||||
end
|
||||
if update
|
||||
File.write( gltf_path, JSON.pretty_generate(gltf) )
|
||||
XRForge.log("✅ writing #{resource['path']}",logfile)
|
||||
system("assimp export #{gltf_path} #{resource['path']} --embed-textures")
|
||||
end
|
||||
end
|
||||
|
||||
XRForge.log(" ", logfile)
|
||||
|
||||
rescue Errno::ENOENT
|
||||
puts "File #{filename} not found"
|
||||
rescue JSON::ParserError
|
||||
puts "Error parsing JSON from #{filename}"
|
||||
rescue => e
|
||||
puts "An error occurred: #{e.message}"
|
||||
end
|
||||
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'json'
|
||||
require_relative './../../xrforge.rb'
|
||||
|
||||
# Check if a filename is provided
|
||||
if ARGV.length != 1
|
||||
puts "Usage: #{$0} <path/to/experience/somefile.xxx>"
|
||||
exit 1
|
||||
end
|
||||
|
||||
filename = ARGV[0]
|
||||
|
||||
require 'base64'
|
||||
require 'json'
|
||||
|
||||
begin
|
||||
|
||||
# Change the directory
|
||||
dir = File.dirname(filename)
|
||||
Dir.chdir( File.dirname(filename) )
|
||||
# Read and parse the JSON file
|
||||
data = JSON.parse( File.read( "datapackage.json" ) )
|
||||
|
||||
ext = File.extname(filename)
|
||||
filenameWithoutExt = File.basename(filename, ext)
|
||||
|
||||
if ! XRForge::MODEL_EXT.any?( ext ) || ext == ".gltf"
|
||||
exit(0) # not a 3d file
|
||||
end
|
||||
|
||||
logfile = File.join( File.dirname(filename), ".xrforge/log.txt" )
|
||||
XRForge.log("✅ generating gltf", logfile)
|
||||
gltf_path = ".xrforge/scene.gltf"
|
||||
system("assimp export #{filename} #{gltf_path} --embed-textures")
|
||||
|
||||
# tag it!
|
||||
if ! data['keywords'].include?('gltf')
|
||||
data['keywords'].push('gltf')
|
||||
File.write("datapackage.json", JSON.pretty_generate(data) )
|
||||
end
|
||||
|
||||
XRForge.log(" ", logfile)
|
||||
|
||||
rescue Errno::ENOENT
|
||||
puts "File #{filename} not found"
|
||||
rescue JSON::ParserError
|
||||
puts "Error parsing JSON from #{filename}"
|
||||
rescue => e
|
||||
puts "An error occurred: #{e.message}"
|
||||
end
|
||||
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#!/bin/sh
|
||||
dir="$(dirname $1)"
|
||||
cd "$dir"
|
||||
echo "[package_godot_zip.sh] zipping godot.zip"
|
||||
|
||||
# overwrite empty godot template project-zip with given URL
|
||||
test -n "$GODOT_TEMPLATE_ZIP" && timeout 50 wget "$GODOT_TEMPLATE_ZIP" -O ~/templates/template_godot.zip
|
||||
|
||||
cp ~/templates/template_godot.zip .xrforge/godot.zip
|
||||
zip .xrforge/godot.zip *.glb *.usdz *.obj
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'json'
|
||||
require_relative './../../xrforge.rb'
|
||||
|
||||
# Check if a filename is provided
|
||||
if ARGV.length != 1
|
||||
puts "Usage: #{$0} <path/to/experience/datapackage.json>"
|
||||
exit 1
|
||||
end
|
||||
|
||||
filename = ARGV[0]
|
||||
JMLHeuristic = /<fireboxroom>.*?<\/fireboxroom>/im
|
||||
|
||||
begin
|
||||
|
||||
# dont run for each file-update
|
||||
if ! filename.end_with?("datapackage.json")
|
||||
exit 0
|
||||
end
|
||||
|
||||
# Change the directory
|
||||
dir = File.dirname(filename)
|
||||
dirPublic = dir.gsub("/mnt/","").gsub("#","%23")
|
||||
Dir.chdir( File.dirname(filename) )
|
||||
# Read and parse the JSON file
|
||||
data = JSON.parse( File.read( "datapackage.json" ) )
|
||||
|
||||
#if data['keywords'].empty? || data['keywords'].include?('janusxr')
|
||||
|
||||
logfile = File.join( File.dirname(filename), ".xrforge/log.txt" )
|
||||
|
||||
XRForge.log("✅ starting build janusXR XR scene", logfile)
|
||||
|
||||
model_file = XRForge.getExperienceFile(data,dir,logfile)
|
||||
|
||||
# Check if a model file was found after the loop
|
||||
if ! model_file
|
||||
XRForge.log("❌ No suitable 3D file found for JanusXR-compatible experience", logfile)
|
||||
exit
|
||||
end
|
||||
|
||||
# Get the value of the environment variable FEDERATE_DRIVE_HOST
|
||||
federate_drive_host = ENV['FEDERATE_DRIVE_HOST']
|
||||
|
||||
autogenerate = true
|
||||
|
||||
if data['description'] && data['description'].match(JMLHeuristic)
|
||||
if data['description'].match(/autogenerate=["']false["']/i)
|
||||
XRForge.log("✅ autogenerate='false' found in JML..keeping this JML",logfile)
|
||||
autogenerate = false
|
||||
jmlMatch = data['description'].match(JMLHeuristic)
|
||||
jml = jmlMatch[0]
|
||||
end
|
||||
end
|
||||
if autogenerate
|
||||
assets = "<assetobject id=\"experience\" src=\"#{federate_drive_host}/#{dirPublic}/#{model_file.gsub("#","%23")}\"/>\n"
|
||||
objects = ""
|
||||
|
||||
# detect audio track sidecarfile as per XR Fragment spec: https://xrfragment.org/#%F0%9F%93%9C%20level0%3A%20File
|
||||
xrf_ext = File.extname(model_file)
|
||||
xrf_base = File.basename(model_file, xrf_ext)
|
||||
data['resources'].each do |resource|
|
||||
ext = File.extname(resource['path'])
|
||||
if ext.match(/mp3/i)
|
||||
base = File.basename(resource['path'], ext)
|
||||
if base == xrf_base
|
||||
XRForge.log("✅ XR Fragments side-car file found: #{resource['path']}",logfile)
|
||||
assets = assets + " <assetsound id=\"soundtrack\" src=\"#{federate_drive_host}/#{dirPublic}/#{resource["path"]}\"/>\n"
|
||||
objects = objects + "<sound id=\"soundtrack\" loop=\"true\"/>\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if ! data['keywords']
|
||||
data['keywords'] = []
|
||||
end
|
||||
|
||||
private = data['keywords'].include?('singleuser') ? "private='true'" : ""
|
||||
# tags to JML rooms *REFACTOR PLEASE*
|
||||
use_local_asset = ""
|
||||
use_local_asset = data['keywords'].include?('room1') ? "use_local_asset=\"room1\"" : use_local_asset
|
||||
use_local_asset = data['keywords'].include?('room2') ? "use_local_asset=\"room2\"" : use_local_asset
|
||||
use_local_asset = data['keywords'].include?('room3') ? "use_local_asset=\"room3\"" : use_local_asset
|
||||
use_local_asset = data['keywords'].include?('room4') ? "use_local_asset=\"room4\"" : use_local_asset
|
||||
use_local_asset = data['keywords'].include?('room5') ? "use_local_asset=\"room5\"" : use_local_asset
|
||||
use_local_asset = data['keywords'].include?('room6') ? "use_local_asset=\"room5\"" : use_local_asset
|
||||
use_local_asset = data['keywords'].include?('room1_pedestal') ? "use_local_asset=\"room1_pedestal\"" : use_local_asset
|
||||
use_local_asset = data['keywords'].include?('room2_pedestal') ? "use_local_asset=\"room2_pedestal\"" : use_local_asset
|
||||
use_local_asset = data['keywords'].include?('room2_narrow') ? "use_local_asset=\"room3_narrow\"" : use_local_asset
|
||||
|
||||
objects = objects + " <object pos=\"0 0 5\" rotation=\"-180 0 180\" lighting=\"false\" collision_id=\"experience\" id=\"experience\" />"
|
||||
|
||||
#janusweb_src = "https://web.janusvr.com/janusweb.js"
|
||||
janusweb_src = ! ENV['DEV'].empty? ? "/janusweb/janusweb.js" : "/janusweb/janusweb.min.js"
|
||||
|
||||
server = ""
|
||||
if ENV['JANUSXR'] && ! ENV['JANUSXR'].empty?
|
||||
serverUrl = federate_drive_host.gsub("://","://presence.")
|
||||
.gsub(/:[0-9].*/,"")
|
||||
.gsub(/\/$/,"")
|
||||
server = "server=#{serverUrl}:5566/"
|
||||
end
|
||||
|
||||
jml = <<~JML
|
||||
<FireBoxRoom>
|
||||
<Assets>
|
||||
#{assets}
|
||||
</Assets>
|
||||
<Room autogenerate="true" #{use_local_asset} #{private} #{server} showavatar="false" voip="none">
|
||||
#{objects}
|
||||
</Room>
|
||||
</FireBoxRoom>
|
||||
<!-- archive.org hints -->
|
||||
<a href="#{federate_drive_host}/#{dirPublic}/#{model_file.gsub("#","%23")}"></a>
|
||||
JML
|
||||
|
||||
data['description'] = data['description'] ? data['description'] : "your description here\n"
|
||||
data['description'] = <<~DESCRIPTION#{data['description']}
|
||||
<!-- Hi there! Below is autogenerated JanusXR Markup (JML). -->
|
||||
<!-- If you want to tweak it, then first disable autogeneration -->
|
||||
<!-- How? make sure to set autogenerate to "false" (see room-tag below) -->
|
||||
<!-- -->
|
||||
<!-- JML info: -->
|
||||
<!-- https://coderofsalvation.github.io/janus-guide/#/examples/markup -->
|
||||
<!-- https://janusxr.org/docs/build/introtojml/index.html -->
|
||||
|
||||
#{jml}
|
||||
DESCRIPTION
|
||||
File.write("datapackage.json", JSON.pretty_generate(data) )
|
||||
end
|
||||
|
||||
html = <<~HTML
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>#{data['title']} - JanusXR</title>
|
||||
</head>
|
||||
<body>
|
||||
<janus-viewer homepage="/" autostart="false">
|
||||
#{jml}
|
||||
</janus-viewer>
|
||||
|
||||
<!-- map URL query args to room-attributes -->
|
||||
<script>
|
||||
args = new URLSearchParams(document.location.search)
|
||||
for( const [k,v] of args.entries()) document.querySelector("Room").setAttribute(k,v)
|
||||
</script>
|
||||
<script src="#{janusweb_src}"></script>
|
||||
<script>
|
||||
elation.config.set("engine.assets.corsproxy", "#{federate_drive_host.gsub(/:[0-9].*/,"")}:5577")
|
||||
elation.config.set("janusweb.network.host", "#{federate_drive_host.gsub(/:[0-9].*/,"")}:5566")
|
||||
elation.config.set("dependencies.host", "#{federate_drive_host.gsub(/:[0-9].*/,"")}");
|
||||
let opts = {
|
||||
uiconfig: `/janusweb/media/assets/webui/${ args.get("networking") == "false" ? "preview" : "default"}.json`,
|
||||
// for more opts see getClientArgs() in
|
||||
// https://github.com/jbaicoianu/janusweb/blob/master/scripts/client.js
|
||||
}
|
||||
// start!
|
||||
elation.janusweb.init(opts)
|
||||
</script>
|
||||
|
||||
<!-- archive.org hints -->
|
||||
<a href="#{federate_drive_host}/#{dirPublic}/#{model_file.gsub("#","%23")}"></a>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
File.write('.xrforge/janusxr.html', html)
|
||||
File.write('.xrforge/scene.jml', jml)
|
||||
|
||||
XRForge.log("✅ generated scene.jml", logfile)
|
||||
XRForge.log("✅ generated janusxr.html", logfile)
|
||||
XRForge.log(" ", logfile)
|
||||
|
||||
# tag it!
|
||||
if ! data['keywords'].include?('janusxr')
|
||||
data['keywords'].push('janusxr')
|
||||
File.write("datapackage.json", JSON.pretty_generate(data) )
|
||||
end
|
||||
|
||||
rescue Errno::ENOENT
|
||||
puts "File #{filename} not found"
|
||||
rescue JSON::ParserError
|
||||
puts "Error parsing JSON from #{filename}"
|
||||
rescue => e
|
||||
puts "An error occurred: #{e.message}"
|
||||
end
|
||||
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'json'
|
||||
require_relative './../../xrforge.rb'
|
||||
|
||||
# Check if a filename is provided
|
||||
if ARGV.length != 1
|
||||
puts "Usage: #{$0} <path/to/experience/somefile.xxx>"
|
||||
exit 1
|
||||
end
|
||||
|
||||
filename = ARGV[0]
|
||||
|
||||
# dont run for each file-update
|
||||
if ! filename.end_with?("datapackage.json")
|
||||
exit 0
|
||||
end
|
||||
|
||||
begin
|
||||
|
||||
# Change the directory
|
||||
dir = File.dirname(filename)
|
||||
Dir.chdir( File.dirname(filename) )
|
||||
# Read and parse the JSON file
|
||||
data = JSON.parse( File.read( "datapackage.json" ) )
|
||||
|
||||
#if data['keywords'].empty? || data['keywords'].include?('mml')
|
||||
|
||||
logfile = File.join( File.dirname(filename), ".xrforge/log.txt" )
|
||||
|
||||
XRForge.log("✅ starting build mml XR scene", logfile)
|
||||
|
||||
model_file = XRForge.getExperienceFile(data,dir,logfile)
|
||||
|
||||
# Check if a model file was found after the loop
|
||||
if ! model_file
|
||||
XRForge.log("❌ No suitable 3D file found for MML-compatible experience", logfile)
|
||||
exit 0
|
||||
end
|
||||
|
||||
# Get the value of the environment variable FEDERATE_DRIVE_HOST
|
||||
federate_drive_host = ENV['FEDERATE_DRIVE_HOST']
|
||||
|
||||
# https://viewer.mml.io/main/v1/?url=https%3A%2F%2Ffoo.org%2Fbar.mml
|
||||
mml = <<~MML
|
||||
<m-model src="#{federate_drive_host}/#{model_file.gsub("#","%23")}" anim-loop="true" anim-enabled="true"></m-model>
|
||||
MML
|
||||
|
||||
File.write('.xrforge/scene.mml', mml)
|
||||
|
||||
XRForge.log("✅ generated scene.mml", logfile)
|
||||
XRForge.log(" ", logfile)
|
||||
|
||||
# tag it!
|
||||
if ! data['keywords'].include?('mml')
|
||||
data['keywords'].push('mml')
|
||||
File.write("datapackage.json", JSON.pretty_generate(data) )
|
||||
end
|
||||
|
||||
rescue Errno::ENOENT
|
||||
puts "File #{filename} not found"
|
||||
rescue JSON::ParserError
|
||||
puts "Error parsing JSON from #{filename}"
|
||||
rescue => e
|
||||
puts "An error occurred: #{e.message}"
|
||||
end
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'json'
|
||||
require_relative './../../xrforge.rb'
|
||||
|
||||
# Check if a filename is provided
|
||||
if ARGV.length != 1
|
||||
puts "Usage: #{$0} <path/to/experience/somefile.xxx>"
|
||||
exit 1
|
||||
end
|
||||
|
||||
filename = ARGV[0]
|
||||
|
||||
begin
|
||||
# Change the directory
|
||||
dir = File.dirname(filename)
|
||||
Dir.chdir( dir )
|
||||
# Read and parse the JSON file
|
||||
data = JSON.parse( File.read( "datapackage.json" ) )
|
||||
|
||||
logfile = File.join( File.dirname(filename), ".xrforge/log.txt" )
|
||||
XRForge.log("✅ starting XR fragments check", logfile)
|
||||
|
||||
model_file = XRForge.getExperienceFile(data,dir,logfile)
|
||||
|
||||
# Check if a model file was found after the loop
|
||||
if ! model_file
|
||||
XRForge.log("❌ No suitable 3D file found for XR Fragments-compatible experience", logfile)
|
||||
exit 0
|
||||
end
|
||||
|
||||
# Check if a model file was found after the loop
|
||||
if model_file
|
||||
XRForge.log("✅ Final model file: '#{model_file}'", logfile)
|
||||
# update datapackage
|
||||
data['main'] = model_file
|
||||
if ! data['keywords'].include?('xrfragments')
|
||||
data['keywords'].push('xrfragments')
|
||||
end
|
||||
File.write("datapackage.json", JSON.pretty_generate(data) )
|
||||
system("assimp export #{model_file} .xrforge/scene.gltf")
|
||||
else
|
||||
XRForge.log("❌ No suitable 3D file found for XR Fragments-compatible experience", logfile)
|
||||
# update datapackage
|
||||
if data['keywords'].include?('xrfragments')
|
||||
data['keywords'].delete('xrfragments')
|
||||
File.write("datapackage.json", JSON.pretty_generate(data) )
|
||||
end
|
||||
end
|
||||
XRForge.log("✅ updating xrfragment tag", logfile)
|
||||
|
||||
XRForge.log(" ", logfile)
|
||||
|
||||
rescue Errno::ENOENT
|
||||
puts "File #{filename} not found"
|
||||
rescue JSON::ParserError
|
||||
puts "Error parsing JSON from #{filename}"
|
||||
rescue => e
|
||||
puts "An error occurred: #{e.message}"
|
||||
end
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'rss'
|
||||
require 'json'
|
||||
require 'open-uri'
|
||||
require 'cgi'
|
||||
require_relative './../../xrforge.rb'
|
||||
|
||||
# Check if a filename is provided
|
||||
if ARGV.length != 1
|
||||
puts "Usage: #{$0} <path/to/experience/somefile.xxx>"
|
||||
exit 1
|
||||
end
|
||||
|
||||
filename = ARGV[0]
|
||||
|
||||
# dont run for each file-update
|
||||
if ! filename.end_with?("datapackage.json")
|
||||
exit 0
|
||||
end
|
||||
|
||||
def generate(aphandle,filename,dir,data,logfile)
|
||||
|
||||
XRForge.log("✅ starting mastodon-post for #{aphandle}", logfile)
|
||||
|
||||
parts = aphandle.split("@")
|
||||
server = parts[2].sub(/@/,"")
|
||||
rssUrl = "https://#{server}/@#{parts[1]}.rss"
|
||||
XRForge.log("✅ checking #{rssUrl}", logfile)
|
||||
|
||||
feed = RSS::Parser.parse(URI.open(rssUrl, 'User-Agent' => 'Ruby-RSS-Client'))
|
||||
|
||||
puts feed.channel.title
|
||||
puts feed.channel.link
|
||||
|
||||
first_item = feed.items.first
|
||||
if first_item
|
||||
description = CGI.unescapeHTML(first_item.description.to_s)
|
||||
.split("\n")[0,4].join("\n") # max 5 lines
|
||||
.gsub(/<[^>]*>/, '') # remove other tags
|
||||
#.gsub(/<br\s*\/?>/i, "\n") # preserve linebreaks
|
||||
#.gsub(/<\/p>/i, "\n") # preserve linebreaks
|
||||
description = description.length > 130 ? description = description[0,130] + " (..)" : description
|
||||
else
|
||||
XRForge.log("❌ did not find post", logfile)
|
||||
exit 0
|
||||
end
|
||||
|
||||
puts description
|
||||
|
||||
# look for first image
|
||||
media_regex = /<media:content url=['"]([^'"]+)['"]/
|
||||
img = feed.to_s.match(media_regex)
|
||||
if img and img[1] and img[1].match(/(png|jpg)/)
|
||||
imgurl = img[1]
|
||||
end
|
||||
|
||||
# generate the final .glb
|
||||
system("/root/templates/mastodon-post/mastodon-post.sh", description, feed.channel.title, feed.channel.link, aphandle, "#{dir}/#{aphandle}.glb")
|
||||
end
|
||||
|
||||
begin
|
||||
# Change the directory
|
||||
dir = File.dirname(filename)
|
||||
Dir.chdir( dir )
|
||||
# Read and parse the JSON file
|
||||
data = JSON.parse( File.read( "datapackage.json" ) )
|
||||
logfile = File.join( File.dirname(filename), ".xrforge/log.txt" )
|
||||
|
||||
data['keywords'].each do |tag|
|
||||
if tag.match?(/^@.*@.*\./) # scan for activitypub handle (@foo@mastodon.online e.g.)
|
||||
generate(tag,filename,dir,data,logfile)
|
||||
end
|
||||
end
|
||||
|
||||
XRForge.log(" ", logfile)
|
||||
|
||||
rescue OpenURI::HTTPError => e
|
||||
# Handle HTTP errors (e.g., 404 not found, 403 forbidden)
|
||||
puts "Error fetching feed: #{e.message}"
|
||||
exit
|
||||
rescue => e
|
||||
# Handle other parsing or connection errors
|
||||
puts "An error occurred: #{e.message}"
|
||||
rescue Errno::ENOENT
|
||||
puts "File #{filename} not found"
|
||||
rescue JSON::ParserError
|
||||
puts "Error parsing JSON from #{filename}"
|
||||
rescue => e
|
||||
puts "An error occurred: #{e.message}"
|
||||
end
|
||||
1
manyfold/root/hook.d/inotify_CREATE/placeholder.sh
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../hourly/placeholder.sh
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
test -f "$1".zip && rm "$1".zip
|
||||
echo "[cleanup_package.sh] deleting $dir.zip"
|
||||
6
manyfold/root/hook.d/inotify_MODIFY/package_experience_zip.sh
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
echo "$1" | grep datapackage || exit 0 # nothing to do
|
||||
dir=$(dirname $1)
|
||||
cd "$dir"
|
||||
echo "[package_experience.sh] zipping $dir.zip"
|
||||
zip -r "$dir".zip $dir/*
|
||||
11
manyfold/root/hook.d/inotify_MODIFY/package_godot_zip.sh
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
echo "$1" | grep datapackage || exit 0 # nothing to do
|
||||
dir=$(dirname $1)
|
||||
cd "$dir"
|
||||
echo "[package_experience.sh] zipping $dir.zip"
|
||||
|
||||
# overwrite empty godot template project-zip with given URL
|
||||
test -n "$GODOT_TEMPLATE_ZIP" && timeout 50 wget "$GODOT_TEMPLATE_ZIP" -O ~/template_godot.zip
|
||||
|
||||
cp ~/template_godot.zip package_godot.zip
|
||||
zip package_godot.zip *.glb *.usdz *.obj
|
||||
1
manyfold/root/hook.d/inotify_MOVED_TO
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
inotify_MODIFY
|
||||
|
Before Width: | Height: | Size: 51 KiB |
|
|
@ -1,154 +0,0 @@
|
|||
{
|
||||
"asset":{
|
||||
"generator":"Khronos glTF Blender I/O v4.4.56",
|
||||
"version":"2.0"
|
||||
},
|
||||
"extensionsUsed":[
|
||||
"KHR_materials_unlit"
|
||||
],
|
||||
"scene":0,
|
||||
"scenes":[
|
||||
{
|
||||
"name":"Scene",
|
||||
"nodes":[
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes":[
|
||||
{
|
||||
"mesh":0,
|
||||
"name":"mastodon-post",
|
||||
"rotation":[
|
||||
0.70710688829422,
|
||||
0,
|
||||
0,
|
||||
0.7071066498756409
|
||||
],
|
||||
"scale":[
|
||||
2.471118211746216,
|
||||
2.471118450164795,
|
||||
2.471118450164795
|
||||
]
|
||||
}
|
||||
],
|
||||
"materials":[
|
||||
{
|
||||
"doubleSided":true,
|
||||
"extensions":{
|
||||
"KHR_materials_unlit":{}
|
||||
},
|
||||
"name":"unlit.001",
|
||||
"pbrMetallicRoughness":{
|
||||
"baseColorTexture":{
|
||||
"index":0
|
||||
},
|
||||
"metallicFactor":0,
|
||||
"roughnessFactor":0.9
|
||||
}
|
||||
}
|
||||
],
|
||||
"meshes":[
|
||||
{
|
||||
"name":"Plane",
|
||||
"primitives":[
|
||||
{
|
||||
"attributes":{
|
||||
"POSITION":0,
|
||||
"NORMAL":1,
|
||||
"TEXCOORD_0":2
|
||||
},
|
||||
"indices":3,
|
||||
"material":0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures":[
|
||||
{
|
||||
"sampler":0,
|
||||
"source":0
|
||||
}
|
||||
],
|
||||
"images":[
|
||||
{
|
||||
"mimeType":"image/png",
|
||||
"name":"mastodon-post",
|
||||
"uri":"mastodon-post.png"
|
||||
}
|
||||
],
|
||||
"accessors":[
|
||||
{
|
||||
"bufferView":0,
|
||||
"componentType":5126,
|
||||
"count":20,
|
||||
"max":[
|
||||
1,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"min":[
|
||||
-1,
|
||||
0,
|
||||
-1
|
||||
],
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":1,
|
||||
"componentType":5126,
|
||||
"count":20,
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":2,
|
||||
"componentType":5126,
|
||||
"count":20,
|
||||
"type":"VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView":3,
|
||||
"componentType":5123,
|
||||
"count":54,
|
||||
"type":"SCALAR"
|
||||
}
|
||||
],
|
||||
"bufferViews":[
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":240,
|
||||
"byteOffset":0,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":240,
|
||||
"byteOffset":240,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":160,
|
||||
"byteOffset":480,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":108,
|
||||
"byteOffset":640,
|
||||
"target":34963
|
||||
}
|
||||
],
|
||||
"samplers":[
|
||||
{
|
||||
"magFilter":9729,
|
||||
"minFilter":9987
|
||||
}
|
||||
],
|
||||
"buffers":[
|
||||
{
|
||||
"byteLength":748,
|
||||
"uri":"mastodon-post.bin"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 176 B |
|
|
@ -1,34 +0,0 @@
|
|||
#!/bin/sh
|
||||
test -n "$5" || { echo "usage: ./mastodon-post.sh <text> <title> <link> <handle> </abs/path/to/out.glb>"; exit 0; }
|
||||
|
||||
out="$5"
|
||||
tmpdir=/tmp/.mastodon-post
|
||||
outdir="$(dirname "$out")"
|
||||
mydir="$(dirname $(readlink -f $0))"
|
||||
bgfile="$outdir"/mastodon-post-bg.png
|
||||
cd $mydir
|
||||
|
||||
set -x
|
||||
# create tmp workspace
|
||||
test -d $tmpdir && rm -rf $tmpdir
|
||||
mkdir $tmpdir
|
||||
cp * $tmpdir/.
|
||||
# use template PNG from modeldir
|
||||
test -f "$bgfile" || cp $tmpdir/mastodon-post-bg.png "$bgfile"
|
||||
cp "$bgfile" $tmpdir/.
|
||||
rm $tmpdir/mastodon-post.png # IMPORTANT: prevent newer/older imagemagick -clobber (non)existence
|
||||
|
||||
magick "$bgfile" \
|
||||
\( -size 580x -background none -pointsize 32 -interline-spacing 10 -fill \#FFF -font Montserrat-SemiBold.ttf "caption:$1" \) \
|
||||
-gravity center -composite \
|
||||
-pointsize 20 \
|
||||
-fill \#FFFFFF -gravity NorthWest -annotate +152+130 "$4" \
|
||||
-fill \#9c9cc9 -gravity NorthWest -annotate +152+160 "$2" \
|
||||
-fill \#9c9cc9 -gravity NorthWest -annotate +52+460 "$3" \
|
||||
-define png:compression-level=9 \
|
||||
-define png:compression-filter=5 \
|
||||
-define png:compression-strategy=1 \
|
||||
$tmpdir/mastodon-post.png
|
||||
|
||||
cd $tmpdir
|
||||
assimp export mastodon-post.gltf $out --embed-textures
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
require 'rexml/document'
|
||||
require 'rexml/formatters/pretty'
|
||||
|
||||
module XRForge
|
||||
|
||||
MODEL_EXT = ['.glb', '.gltf', '.blend', '.usdz', '.obj', '.dae', '.x3d']
|
||||
|
||||
def self.log(message, filename)
|
||||
# Append the log entry to the log file
|
||||
File.open(filename, 'a') do |file|
|
||||
file.write("#{message}\n")
|
||||
end
|
||||
puts("#{message}\n")
|
||||
end
|
||||
|
||||
# ==============================================================================
|
||||
# 1. JSON (Ruby Hash) to XML String
|
||||
# ==============================================================================
|
||||
|
||||
# Internal recursive helper for converting Hash/Array structure to REXML.
|
||||
# Handles attribute conversion (@key -> attribute), hash nesting, and arrays
|
||||
# (which create multiple sibling elements of the same name).
|
||||
#
|
||||
# @param data [Hash, Array, String] The data fragment.
|
||||
# @param p [REXML::Element] The parent REXML element.
|
||||
def self.j2x_rec(data, p)
|
||||
# Check if data is a Hash (for attributes and children)
|
||||
if data.is_a?(Hash)
|
||||
data.each { |k, v| k.start_with?('@') ? p.attributes[k[1..-1]] = v.to_s : j2x_rec(v, p.add_element(k)) }
|
||||
# Check if data is an Array (for repeated elements)
|
||||
elsif data.is_a?(Array)
|
||||
# The parent in this context (e.g., <obj>) is already created. For each array item,
|
||||
# we need to create a new element with the same name as the current parent's *name*
|
||||
# and attach it to the parent's *parent*.
|
||||
# This requires looking up the parent's parent, which breaks terseness.
|
||||
# We must use a simpler recursive structure for compactness.
|
||||
|
||||
# We assume array structure requires us to create a new element named by the
|
||||
# parent, and assign attributes/children recursively.
|
||||
data.each { |v| j2x_rec(v, p) } # Recursive call to continue processing children
|
||||
# Handle text content
|
||||
else
|
||||
p.text = data.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# The entry point for converting Ruby Hash to XML string.
|
||||
#
|
||||
# @param data [Hash] The root hash structure.
|
||||
# @return [String] The resulting compact XML string.
|
||||
def self.json2xml(data)
|
||||
# Corrected recursive logic for arrays that are values (e.g., "obj" => [..])
|
||||
j2x_map = lambda do |d, p_name, p|
|
||||
if d.is_a?(Hash)
|
||||
d.each { |k, v| k.start_with?('@') ? p.attributes[k[1..-1]] = v.to_s : j2x_map.call(v, k, p.add_element(k)) }
|
||||
elsif d.is_a?(Array)
|
||||
# If the value is an array, we iterate, create a sibling element for each, and recurse
|
||||
d.each { |v| j2x_map.call(v, p.name, p.parent.add_element(p.name)) }
|
||||
p.remove # Remove the placeholder element created before hitting the array
|
||||
else
|
||||
p.text = d.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Initialize Document and process the single root key
|
||||
doc = REXML::Document.new
|
||||
data.each { |k, v| j2x_map.call(v, k, doc.add_element(k)) }
|
||||
|
||||
# Output with 2-space indentation
|
||||
o = ""; f = REXML::Formatters::Default.new; f.indentation = 2; f.write(doc.root, o); o.strip
|
||||
end
|
||||
|
||||
# ==============================================================================
|
||||
# 2. XML String to JSON (Ruby Hash)
|
||||
# ==============================================================================
|
||||
|
||||
# Internal recursive helper for converting REXML Element to Hash structure.
|
||||
# Handles attribute conversion (attribute -> @key), text, and array creation for siblings.
|
||||
#
|
||||
# @param e [REXML::Element] The XML element.
|
||||
# @return [Hash, String] The resulting hash or simple text value.
|
||||
def self.x2j_rec(e)
|
||||
h = {}; e.attributes.each { |k, v| h["@#{k}"] = v } # Attributes first
|
||||
|
||||
e.elements.each do |c| # Iterate children
|
||||
v = x2j_rec(c)
|
||||
if h.key?(c.name)
|
||||
h[c.name] = [h[c.name]] unless h[c.name].is_a?(Array)
|
||||
h[c.name] << v
|
||||
else
|
||||
h[c.name] = v
|
||||
end
|
||||
end
|
||||
|
||||
# Check for text content if no children were processed
|
||||
t = e.get_text.to_s.strip; return t if t && !t.empty? && h.empty?; return h
|
||||
end
|
||||
|
||||
# The entry point for converting XML string to Ruby Hash.
|
||||
#
|
||||
# @param xmlstr [String] The XML string.
|
||||
# @return [Hash] The resulting Ruby hash structure.
|
||||
def self.xml2json(xmlstr)
|
||||
doc = REXML::Document.new(xmlstr)
|
||||
root = doc.root
|
||||
{ root.name => x2j_rec(root) }
|
||||
end
|
||||
|
||||
def self.getExperienceFile(datapackage, dir, logfile)
|
||||
model_file = nil # Initialize model_file to nil
|
||||
base_name = ""
|
||||
# Extract the desired field (assuming the field is named 'model_file')
|
||||
thumb_file = datapackage['image']
|
||||
if thumb_file && ! thumb_file.empty?
|
||||
# HEURISTIC: XR Fragments sidecarfile https://xrfragment.org/#%F0%9F%93%9C%20level0%3A%20File
|
||||
# Get the base name of the thumbnail file without its extension
|
||||
base_name = File.basename(thumb_file, File.extname(thumb_file))
|
||||
XRForge.log("✅ thumbnail sidecar-file '#{thumb_file}' detected", logfile)
|
||||
# Loop over the list of extensions
|
||||
MODEL_EXT.each do |ext|
|
||||
# Construct the filename with the current extension
|
||||
filename = "#{base_name}#{ext}"
|
||||
|
||||
# Check if the file exists
|
||||
if File.exist?(filename)
|
||||
XRForge.log("✅ 3D file '#{filename}' detected (heuristic: XRFragment sidecarfile)", logfile)
|
||||
model_file = "#{dir.gsub("/mnt/","")}/#{filename}" # Store the found filename
|
||||
break # Stop the loop once a file is found
|
||||
else
|
||||
# Log a message for the file that was not found, but don't stop
|
||||
XRForge.log("⚠️ 3D file '#{filename}' not detected", logfile)
|
||||
end
|
||||
end
|
||||
else
|
||||
# HEURISTIC: get first suitable 3D file
|
||||
datapackage['resources'].each do |resource|
|
||||
ext = File.extname(resource['path'])
|
||||
if MODEL_EXT.include?( ext )
|
||||
model_file = resource['path']
|
||||
XRForge.log("✅ 3D file '#{model_file}' detected (heuristic: first-found)", logfile)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return File.basename(model_file)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
which rclone &>/dev/null || { echo "❌ rclone not installed"; exit 1; }
|
||||
|
||||
test -d /mnt/experiences || { echo "❌ /mnt/models does not exist"; exit 1; }
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
#!/bin/sh
|
||||
experience=/mnt/templates/xrfragments/\#4
|
||||
dir=/tmp/hook-extract-textures
|
||||
xrfdir=$dir/.xrforge
|
||||
|
||||
which assimp || {
|
||||
echo "❌ assimp was not installed";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
mkdir -p $xrfdir || true
|
||||
rm $experience/*_img*.* || true
|
||||
/root/hook.d/experience_updated/*-extract-textures.rb $experience/datapackage.json 2>&1 \
|
||||
| grep "Wrote texture 0" || {
|
||||
echo "❌ $experience/murial3D_img0.png was not written";
|
||||
exit 1;
|
||||
}
|
||||
test -f $experience/murial3D_img0.png || {
|
||||
echo "❌ $experience/murial3D_img0.png was not written";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
rm -rf $dir
|
||||
exit 0
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
#!/bin/sh
|
||||
experience=/mnt/templates/xrfragments/\#4
|
||||
dir=/tmp/hook-compile-textures
|
||||
xrfdir=$dir/.xrforge
|
||||
|
||||
which assimp || {
|
||||
echo "❌ assimp was not installed";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
mkdir -p $xrfdir || true
|
||||
/root/hook.d/experience_updated/*-compile-textures.rb $experience/datapackage.json 2>&1 | grep "wrote output" || {
|
||||
echo "❌ $experience/murial3D.glb was not updated";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
rm -rf $dir
|
||||
exit 0
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
#!/bin/sh
|
||||
dir=/tmp/11-hook
|
||||
xrfdir=$dir/.xrforge
|
||||
mkdir -p $xrfdir
|
||||
echo 1 > $xrfdir/log.txt
|
||||
/root/hook.d/experience_updated/*-reset-log.sh $dir/datapackage.json
|
||||
test "$(cat $xrfdir/log.txt)" = "1" && {
|
||||
echo "❌ log.txt was not reset";
|
||||
exit 1;
|
||||
}
|
||||
rm -rf $dir
|
||||
exit 0
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
#!/bin/sh
|
||||
dir=/tmp/hook-package_godot_zip
|
||||
xrfdir=$dir/.xrforge
|
||||
mkdir -p $xrfdir
|
||||
touch $xrfdir/foo.glb
|
||||
/root/hook.d/experience_updated/*-package_godot_zip.sh $dir/datapackage.json
|
||||
test -f $xrfdir/godot.zip || {
|
||||
echo "❌ godot.zip was not created";
|
||||
exit 1;
|
||||
}
|
||||
rm -rf $dir
|
||||
exit 0
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
#!/bin/sh
|
||||
id=janusxr
|
||||
dir=/tmp/hook-package_$id
|
||||
xrfdir=$dir/.xrforge
|
||||
mkdir -p $xrfdir
|
||||
|
||||
testjml(){
|
||||
/root/hook.d/experience_updated/*-package_$id.rb $dir/datapackage.json
|
||||
test -f $xrfdir/scene.jml || {
|
||||
echo "❌ scene.jml was not created";
|
||||
exit 1;
|
||||
}
|
||||
test -f $xrfdir/janusxr.html || {
|
||||
echo "❌ janusxr.html was not created";
|
||||
exit 1;
|
||||
}
|
||||
}
|
||||
|
||||
echo "### test xrfragment heuristic"
|
||||
touch $dir/foo.png
|
||||
touch $dir/foo.glb
|
||||
echo '{
|
||||
"image":"foo.png",
|
||||
"resources": [{"path":"foo.glb"}]
|
||||
}' > $dir/datapackage.json
|
||||
testjml
|
||||
|
||||
echo "### test first-suitable-3d-file heuristic"
|
||||
rm $dir/foo.png
|
||||
echo '{
|
||||
"resources": [{"path":"foo.glb"}]
|
||||
}' > $dir/datapackage.json
|
||||
testjml
|
||||
|
||||
rm -rf $dir
|
||||
exit 0
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#!/bin/sh
|
||||
test $(ps aux | grep run-janus-server | wc -l) = 2 || {
|
||||
echo "❌ janus-server is not running"
|
||||
exit 1;
|
||||
}
|
||||
echo "✅ janus-server is running"
|
||||
|
||||
test $(ps aux | grep run-corsanywhere | wc -l) = 2 || {
|
||||
echo "❌ corsanywhere is not running"
|
||||
exit 1;
|
||||
}
|
||||
echo "✅ corsanywhere is running"
|
||||
|
||||
|
||||
|
||||
5
manyfold/test/rclone.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
which rclone &>/dev/null || { echo "[!] rclone not installed"; exit 0; }
|
||||
|
||||
test -d /mnt/models || { echo "[!] /mnt/models does not exist"; exit 0; }
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo ""
|
||||
echo "running tests in /test/*"
|
||||
echo ""
|
||||
|
||||
error(){ echocolor "❌" "$*"; exit 1; }
|
||||
ok(){ echocolor "✅" "$*"; return 0; }
|
||||
echocolor(){ printf "\033[96m%s\033[0m \033[95m%s\033[0m %s\n" "$1" "$2" "$3"; }
|
||||
|
||||
find -L /test/* -type f -executable -maxdepth 1 | grep -v runtests | sort -V | while read testscript; do
|
||||
echo "🛠 $testscript"
|
||||
{
|
||||
$testscript "$@" 2>&1 && ok "$testscript" || error "$testscript"
|
||||
} | awk '{ print " | "$0 }'
|
||||
done
|
||||
|
|
@ -105,22 +105,14 @@ class Components::ModelCard < Components::Base
|
|||
def actions
|
||||
div class: "card-footer" do
|
||||
div class: "row" do
|
||||
div class: "col" do
|
||||
#open_button
|
||||
#whitespace
|
||||
if ! @model.tags.where(name: "singleuser" ).any?
|
||||
a alt: "start a meeting at this location", href: ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/janusxr.html", target: "_blank", class: "btn btn-secondary btn-sm" do
|
||||
i class: "bi bi-telephone"
|
||||
img src: "/assets/janusxr.svg", style: "width: 16px; margin-left: 7px; transform: translate(0px,-2px);"
|
||||
end
|
||||
end
|
||||
whitespace
|
||||
status_badges @model
|
||||
end
|
||||
div class: "col" do
|
||||
#open_button
|
||||
#whitespace
|
||||
status_badges @model
|
||||
end
|
||||
div class: "col col-auto" do
|
||||
whitespace
|
||||
BurgerMenu do
|
||||
#DropdownItem(icon: "app", label: "Open in Godot Web" , path: "/godot/?url="+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/package_godot.zip", aria_label: translate("components.model_card.edit_button.label", name: @model.name), target: "_blank" )
|
||||
DropdownItem(icon: "app", label: "Open in Godot Web" , path: "/godot/?url="+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/package_godot.zip", aria_label: translate("components.model_card.edit_button.label", name: @model.name), target: "_blank" )
|
||||
DropdownItem(icon: "pencil", label: t("components.model_card.edit_button.text"), path: model_path(@model), aria_label: translate("components.model_card.edit_button.label", name: @model.name))
|
||||
|
||||
DropdownItem(icon: "trash", label: t("components.model_card.delete_button.text"), path: model_path(@model), method: :delete, aria_label: translate("components.model_card.delete_button.label", name: @model.name), confirm: translate("models.destroy.confirm")) if policy(@model).destroy?
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Components::PreviewFrame < Components::Base
|
||||
include Phlex::Rails::Helpers::ImageTag
|
||||
|
||||
register_value_helper :policy_scope
|
||||
|
||||
def initialize(object:)
|
||||
@object = object
|
||||
end
|
||||
|
||||
def before_template
|
||||
@file = @object.is_a?(Model) ? @object.preview_file : policy_scope(@object.models).first&.preview_file
|
||||
end
|
||||
|
||||
def view_template
|
||||
|
||||
a href: ENV['FEDERATE_DRIVE_HOST']+"/"+@file.model.library.name+"/"+@file.model.path.gsub("#","%23")+"/.xrforge/janusxr.html?networking=false", target: "_blank" do
|
||||
#a href: "/view?#{model_model_file_path(@file.model, @file, format: @file.extension)}", target:"_blank", alt: "launch single-user experience" do
|
||||
if @file
|
||||
local
|
||||
elsif @object.remote?
|
||||
remote
|
||||
else
|
||||
empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def local
|
||||
if @file.is_image?
|
||||
image model_model_file_path(@file.model, @file, format: @file.extension, derivative: "preview"), @file.name
|
||||
elsif @file.is_renderable?
|
||||
div class: "card-img-top #{"sensitive" if needs_hiding?}" do
|
||||
Renderer file: @file
|
||||
end
|
||||
else
|
||||
empty
|
||||
end
|
||||
end
|
||||
|
||||
def remote
|
||||
preview_data = @object.federails_actor&.extensions&.dig("preview")
|
||||
case preview_data&.dig("type")
|
||||
when "Image"
|
||||
image preview_data["url"], preview_data["summary"]
|
||||
when "Document"
|
||||
div class: "card-img-top #{"sensitive" if needs_hiding?}" do
|
||||
iframe(
|
||||
scrolling: "no",
|
||||
srcdoc: safe([
|
||||
"<html><body style=\"margin: 0; padding: 0; aspect-ratio: 1\">",
|
||||
preview_data["content"],
|
||||
"</body></html>"
|
||||
].join),
|
||||
title: preview_data["summary"]
|
||||
)
|
||||
end
|
||||
else
|
||||
empty
|
||||
end
|
||||
end
|
||||
|
||||
def needs_hiding?
|
||||
return false unless current_user.nil? || current_user.sensitive_content_handling.present?
|
||||
case @object.class
|
||||
when Model
|
||||
@object.sensitive
|
||||
when Collection
|
||||
@file.model.sensitive
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def empty
|
||||
div class: "preview-empty" do
|
||||
p { t("components.model_card.no_preview") }
|
||||
end
|
||||
end
|
||||
|
||||
def image(url, alt)
|
||||
div class: "card-img-top card-img-top-background", style: "background-image: url(#{url})"
|
||||
image_tag url, class: "card-img-top image-preview #{"sensitive" if needs_hiding?}", alt: alt, style: "position:absolute; top:0"
|
||||
end
|
||||
end
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="<%= I18n.locale %>" data-controller="i18n">
|
||||
<head>
|
||||
<title><%= @title || site_name %></title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<%= csrf_meta_tags %>
|
||||
<%= tag.meta name: "csp-nonce", content: content_security_policy_nonce if content_security_policy_nonce %>
|
||||
<%= favicon_link_tag "roundel.svg" %>
|
||||
<%= tag.link rel: "apple-touch-icon", href: asset_path("square-180.png") %>
|
||||
<%= tag.meta name: "apple-mobile-web-app-title", content: site_name %>
|
||||
<%= javascript_include_tag "application", nonce: true, defer: true %>
|
||||
<%= stylesheet_link_tag "themes/#{SiteSettings.theme}", nonce: true %>
|
||||
<%= stylesheet_link_tag "/assets/xrforge.css" %>
|
||||
<%= turbo_refreshes_with method: :morph, scroll: :preserve %>
|
||||
<%= tag.meta name: "robots", content: @indexing_directives if @indexing_directives.presence %>
|
||||
<%= yield :head %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%= skip_link "content", t(".skip_to_content") %>
|
||||
<%= render "application/navbar" %>
|
||||
<%= yield :breadcrumbs %>
|
||||
<main class="container-fluid" id="content">
|
||||
<div>
|
||||
<% if notice %>
|
||||
<p class="alert alert-info">
|
||||
<%= icon "info-circle-fill", t(".alert.info") %>
|
||||
<%= notice %>
|
||||
</p>
|
||||
<% end %>
|
||||
<% if alert %>
|
||||
<p class="alert alert-danger">
|
||||
<%= icon "x-octagon-fill", t(".alert.danger") %>
|
||||
<%= alert %>
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="pt-3">
|
||||
<%= yield %>
|
||||
</div>
|
||||
</main>
|
||||
<%= render "application/footer" %>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<%= yield :page_header %>
|
||||
|
||||
<div class="row row-cols-md-2 mt-2">
|
||||
<div class="col-md-9" id="item_list">
|
||||
|
||||
<% if ENV['DEV'] %>
|
||||
<a target="_blank" href="<%=ENV['FEDERATE_DRIVE_HOST']%>/janusweb/index.html#janus.url=<%=ENV['FEDERATE_DRIVE_HOST']%>/janusweb/portalAR.xml">
|
||||
<img src="/assets/lobby.png" style="width:100%; border-radius:10px;margin-bottom:17px"/>
|
||||
</a>
|
||||
<% end %>
|
||||
|
||||
<%= yield :items %>
|
||||
</div>
|
||||
<div class="col-md-3" id="sidebar">
|
||||
<% action_content = yield :actions %>
|
||||
<%= card(:secondary, t(".actions_heading"), class: "action-card") { action_content } if action_content.present? %>
|
||||
<%= yield :sidebar %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% parent_layout "application" %>
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
<%= form_with model: @model do |form| %>
|
||||
<%= text_input_row form, :name %>
|
||||
|
||||
<%= render "tags_edit", form: form, name: "model[tag_list]", value: (@model.tags.order(taggings_count: :desc, name: :asc).map { |tag| tag.name }).join(","), label: translate(".tags"), tags: @available_tags %>
|
||||
<div style="margin-left: 97px; max-height: 333px; overflow: scroll; margin-bottom: 50px; border: 1px solid #CCC; border-radius: 5px; margin-right:10px; background:black; padding:20px;">
|
||||
<b>Available tags:</b><br>
|
||||
<%= render 'models/tags_info' %>
|
||||
</div>
|
||||
|
||||
<%= collection_select_input_row form, :preview_file, @model.valid_preview_files, :id, :name, help: t(".preview_file.help") %>
|
||||
|
||||
<%= collection_select_input_row form,
|
||||
:creator, @creators, :id, :name_with_domain,
|
||||
include_blank: true,
|
||||
selected: @default_creator&.id,
|
||||
button: (if policy(:creator).new?
|
||||
{
|
||||
path: new_creator_path,
|
||||
label: t("creators.general.new")
|
||||
}
|
||||
end) %>
|
||||
|
||||
<% if SiteSettings.show_libraries || current_user.is_administrator? %>
|
||||
<%= unless @model.contains_other_models?
|
||||
collection_select_input_row form,
|
||||
:library, policy_scope(Library).all, :id, :name,
|
||||
include_blank: true
|
||||
end %>
|
||||
<% end %>
|
||||
|
||||
|
||||
<%= collection_select_input_row form,
|
||||
:collection, @collections, :id, :name_with_domain,
|
||||
include_blank: true,
|
||||
button: (if policy(:collection).new?
|
||||
{
|
||||
path: new_collection_path,
|
||||
label: t("collections.general.new")
|
||||
}
|
||||
end) %>
|
||||
|
||||
<%= render "links_form", form: form %>
|
||||
<%= text_input_row form, :caption %>
|
||||
<%= rich_text_input_row form, :notes, help: t(".notes.help_html") %>
|
||||
<%= select_input_row form, :license, license_select_options(selected: @model.license), include_blank: true %>
|
||||
<%= checkbox_input_row form, :sensitive %>
|
||||
<%= select_input_row form, :indexable, indexable_select_options(form.object) %>
|
||||
<%= select_input_row form, :ai_indexable, ai_indexable_select_options(form.object) if SiteSettings.allow_ai_bots %>
|
||||
<%= render "caber_relations_form", form: form %>
|
||||
|
||||
<%= form.submit "Save", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
<table class="table">
|
||||
<tr>
|
||||
<td>
|
||||
<a class="badge rounded-pill bg-secondary tag">menu</a>
|
||||
</td>
|
||||
<td>
|
||||
This will generate a navigator-menu <b>into</b> your main 3D file.<br>
|
||||
The links can be edited <%= link_to "here", edit_model_path(@model) %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a class="badge rounded-pill bg-secondary tag">your@mastodon.online</a>
|
||||
</td>
|
||||
<td>
|
||||
Simply enter your mastodon address as tag, and XRForge will generate a 3D model, based on your last post on mastodon.<br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<Hr/>
|
||||
<b>JanusXR tags:</b>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td style="vertical-align:top">
|
||||
<a class="badge rounded-pill bg-secondary tag">room1</a><br>
|
||||
<a class="badge rounded-pill bg-secondary tag">room2</a><br>
|
||||
<a class="badge rounded-pill bg-secondary tag">room3</a><br>
|
||||
<a class="badge rounded-pill bg-secondary tag">room4</a><br>
|
||||
<a class="badge rounded-pill bg-secondary tag">room5</a><br>
|
||||
<a class="badge rounded-pill bg-secondary tag">room6</a><br>
|
||||
<a class="badge rounded-pill bg-secondary tag">room1_pedestal</a><br>
|
||||
<a class="badge rounded-pill bg-secondary tag">room2_pedestal</a><br>
|
||||
<a class="badge rounded-pill bg-secondary tag">room2_narrow</a><br>
|
||||
</td>
|
||||
<td>
|
||||
This wraps a room around the 3D asset:<br><Br>
|
||||
<img src="/assets/jml_templates.png" style="width:100%; max-width:800px"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a class="badge rounded-pill bg-secondary tag">singleuser</a>
|
||||
</td>
|
||||
<td>
|
||||
This disables multiuser mode (the <a alt="start a meeting at this location" href="#" class="btn btn-secondary btn-sm"><i class="bi bi-telephone"></i></a> button).<br>
|
||||
Set this tag for experiences (meditation, personal productivity space) which are better off without multiple users.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
@ -1,365 +1,217 @@
|
|||
<% content_for :head do %>
|
||||
<%= tag.meta property: "og:type", content: "website" %>
|
||||
<%= tag.meta property: "og:image", content: model_model_file_url(@model, @model.preview_file, format: @model.preview_file.extension) if @model.preview_file&.is_image? && !@model.sensitive %>
|
||||
<%= tag.meta name: "description", content: truncate(sanitize(@model.notes), length: 80) if @model.notes.present? %>
|
||||
<%= tag.meta name: "fediverse:creator", content: @model.creator.federails_actor.at_address if @model.creator %>
|
||||
<%= tag.link rel: "alternate", type: Mime[:oembed], href: model_url(@model, format: :oembed), title: @model.name %>
|
||||
<% end %>
|
||||
|
||||
<div itemscope itemtype="https://schema.org/3DModel">
|
||||
<%= turbo_stream_from @model %>
|
||||
<h1>
|
||||
<span itemprop="name">
|
||||
<a contenteditable="plaintext-only"
|
||||
data-controller="editable"
|
||||
data-action="focus->editable#onFocus blur->editable#onBlur"
|
||||
data-editable-field="model[name]"
|
||||
data-editable-path="<%= model_path(@model) %>"><%= @model.name %></a>
|
||||
</span>
|
||||
<%= link_to icon("search", t(".search")),
|
||||
"https://yeggi.com/q/#{ERB::Util.url_encode(@model.name)}/",
|
||||
class: "btn btn-outline", target: "manyfold_search", rel: "noreferrer" %>
|
||||
</h1>
|
||||
|
||||
<div class="row row-cols-md-2 mt-2">
|
||||
<div class="col-md-9" id="item_list">
|
||||
|
||||
<% if @locked_files > 0 %>
|
||||
<div class="alert alert-info"><%= t(".preview", count: @locked_files) %></div>
|
||||
<% end %>
|
||||
|
||||
<%= render "image_carousel", images: @images %>
|
||||
|
||||
<%= card(:secondary) do %>
|
||||
<p class="card-text" itemprop="description"><%= markdownify @model.notes %></p>
|
||||
<% end if @model.notes.present? %>
|
||||
|
||||
<% if @num_files > 0 %>
|
||||
<h2><a name="files"><%= t(".files") %></a></h2>
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 mb-4">
|
||||
<%= render partial: "file", collection: (@groups.delete(nil)) + @images %>
|
||||
</div>
|
||||
<% @groups.each_pair do |group, files| %>
|
||||
<h3><a name="<%= group.parameterize %>"><%= group.strip.careful_titleize %>*</a></h3>
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 mb-4">
|
||||
<%= render partial: "file", collection: files %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3" id="sidebar">
|
||||
|
||||
<%= card :secondary, "XR networks" do %>
|
||||
<table class="table table-borderless table-sm">
|
||||
<% if ENV['FEDERATE_DRIVE_HOST'].present? %>
|
||||
|
||||
<% if @model.tags.where(name: "xrfragments" ).any? %>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="/assets/janusxr.svg" style="width: 16px; transform: translate(0px,-2px);">
|
||||
</td>
|
||||
<td>
|
||||
<%
|
||||
fediURL = ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/janusxr.html"
|
||||
%>
|
||||
<%= link_to "JanusXR", fediURL, target:"_blank" %>
|
||||
<a href="" target="_blank"><i class="bi bi-link-45deg"></i></a>
|
||||
<label for="toggle_janusxr"><i class="bi bi-info-circle"></i></label>
|
||||
<div class="toggle-box">
|
||||
<input type="checkbox" id="toggle_janusxr" hidden>
|
||||
<div class="hidden-tooltip">
|
||||
<i class="bi bi-arrow-90deg-up"></i>
|
||||
<small>
|
||||
Click to view this page in a JanusXR (JML) browser like JanusWeb.<br>
|
||||
<a href="https://janusxr.org/" target="_blank">JanusXR</a> is an Open XR content-weblayer since 2013, which allows existing webpages to become spatial.<br>
|
||||
It is Free and Opensource, and allows you to meet others in this experience (avatars, chat and voice etc).<br>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<%
|
||||
# lets generate a JanusXR spatial layer for the current scene
|
||||
jmlfile = @model.library.path+"/"+@model.path+"/.xrforge/scene.jml"
|
||||
jml = ""
|
||||
if File.exist?(jmlfile)
|
||||
jml = File.read(jmlfile)
|
||||
end
|
||||
%>
|
||||
|
||||
<!-- JML (https://janusxr.org) spatial markup -->
|
||||
|
||||
<%== jml %>
|
||||
|
||||
<% end %>
|
||||
|
||||
<% if @model.tags.where(name: "xrfragments" ).any? %>
|
||||
<tr>
|
||||
<td>⁂</td>
|
||||
<td>
|
||||
<%= link_to "XR Fragments", ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/scene.gltf" %>
|
||||
<label for="toggle_gltf"><i class="bi bi-info-circle"></i></label>
|
||||
<div class="toggle-box">
|
||||
<input type="checkbox" id="toggle_gltf" hidden>
|
||||
<div class="hidden-tooltip">
|
||||
<i class="bi bi-arrow-90deg-up"></i>
|
||||
<small>
|
||||
This is the glTF JSON URL with embedded hyperlinks for immersive navigation to other 3D scene-files, as per the <a href="https://xrfragment.org" target="_blank">XR Fragments</a> spec.<br>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
|
||||
<% if SiteSettings.federation_enabled? %>
|
||||
<tr>
|
||||
<td>⁂</td>
|
||||
<td><% if @model.remote? %>
|
||||
<%= link_to @model.federails_actor.at_address, @model.federails_actor.profile_url, target: "new" %>
|
||||
<% else %>
|
||||
<%= @model.federails_actor.short_at_address %>
|
||||
<%= render Components::CopyButton.new(text: @model.federails_actor.at_address) %>
|
||||
<% end %>
|
||||
<label for="toggle_activitypub"><i class="bi bi-info-circle"></i></label>
|
||||
<div class="toggle-box">
|
||||
<input type="checkbox" id="toggle_activitypub" hidden>
|
||||
<div class="hidden-tooltip">
|
||||
<i class="bi bi-arrow-90deg-up"></i>
|
||||
<small>
|
||||
This is the <a href="https://en.wikipedia.org/wiki/Fediverse" target="_blank">fediverse</a> activitypub address of this experience.<br>
|
||||
Follow updates by copy/pasting it into ActivityPub <a href="https://codeberg.org/fediverse/delightful-fediverse-clients" target="_blank">clients</a>.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
|
||||
<% if @model.tags.where(name: "xrfragments" ).any? %>
|
||||
<tr>
|
||||
<td>⁂</td>
|
||||
<td>
|
||||
<%= link_to "MML", "https://viewer.mml.io/main/v1/?url="+URI.encode_www_form_component(ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/scene.mml"), target:"_blank" %>
|
||||
<a href="<%= ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/scene.mml" %>" target="_blank"><i class="bi bi-link-45deg"></i></a>
|
||||
<label for="toggle_mml"><i class="bi bi-info-circle"></i></label>
|
||||
<div class="toggle-box">
|
||||
<input type="checkbox" id="toggle_mml" hidden>
|
||||
<div class="hidden-tooltip">
|
||||
<i class="bi bi-arrow-90deg-up"></i>
|
||||
<small>
|
||||
This is the MML address.<br>
|
||||
Metaverse Markup Language (<a href="https://mml.io" target="_blank">MML</a>) is an open markup language used to define experiences.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</table>
|
||||
<% end %>
|
||||
|
||||
|
||||
<%= card :secondary, t(".model_details") do %>
|
||||
<table class="table table-borderless table-sm">
|
||||
|
||||
<% if @model.creator %>
|
||||
<tr>
|
||||
<td><%= icon "person", Creator.model_name.human %></td>
|
||||
<td><%= link_to @model.creator.name, @model.creator, itemprop: "author" %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<tr>
|
||||
<td>
|
||||
<i class="bi bi-journal-check" role="img"></i>
|
||||
</td>
|
||||
<td>
|
||||
<%= link_to "build log", ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/log.txt", target: "_blank" %>
|
||||
<label for="toggle_log"><i class="bi bi-info-circle"></i></label>
|
||||
<div class="toggle-box">
|
||||
<input type="checkbox" id="toggle_log" hidden>
|
||||
<div class="hidden-tooltip" style="max-height:400px">
|
||||
<i class="bi bi-arrow-90deg-up"></i>
|
||||
<small>
|
||||
This is the build log of XR Forge.<br>
|
||||
When you add files, they are processed, validated (for <a href="https://xrfragment.org" target="_blank">XR Fragment</a> compliance).<br>
|
||||
Features can be toggled via tags:<br>
|
||||
<br>
|
||||
<%= render 'models/tags_info' %>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<i class="bi bi-controller" role="img"></i>
|
||||
</td>
|
||||
<td>
|
||||
<%= link_to "Godot project", ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/godot.zip" %>
|
||||
<label for="toggle_godot"><i class="bi bi-info-circle"></i></label>
|
||||
<div class="toggle-box">
|
||||
<input type="checkbox" id="toggle_godot" hidden>
|
||||
<div class="hidden-tooltip">
|
||||
<i class="bi bi-arrow-90deg-up"></i>
|
||||
<small>
|
||||
This is a Godot project which wraps your (3D file) experience.<br>
|
||||
<a href="https://godot.org" target="_blank">Godot</a> is a Free and Opensource Game engine.<br>
|
||||
The Godot project is basically its own XR Fragment browser (which you can extend).<br><br>
|
||||
<b>WARNING</b>: use <a href="https://en.wikipedia.org/wiki/Progressive_enhancement" target="_blank">progressive enhancement</a> so your 3D file experience will always run in other <a href="https://xrfragment.org" target="_blank">XR Fragment</a> viewers.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<% if @model.collection %>
|
||||
<tr>
|
||||
<td><%= icon "collection", Collection.model_name.human(count: 100) %></td>
|
||||
<td><%= link_to @model.collection.name, models_path({collection: @model.collection}.merge(@filter&.to_params)) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if SiteSettings.show_libraries || current_user&.is_administrator? %>
|
||||
<tr>
|
||||
<td><%= icon "boxes", Library.model_name.human %></td>
|
||||
<td><%= link_to @model.library.name, models_path({library: @model.library}.merge(@filter&.to_params)) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if @model.license %>
|
||||
<tr>
|
||||
<td><%= icon "card-heading", t(".license") %></td>
|
||||
<td>
|
||||
<%= Spdx.licenses[@model.license]&.fetch("reference") ?
|
||||
link_to(t_license(@model.license), Spdx.licenses[@model.license]["reference"], itemprop: "license") :
|
||||
t_license(@model.license) %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if @model.sensitive %>
|
||||
<tr>
|
||||
<td><%= icon("explicit", Model.human_attribute_name(:sensitive)) %></td>
|
||||
<td>
|
||||
<%= Model.human_attribute_name(:sensitive) %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<tr>
|
||||
<td><%= icon "folder", t(".path") %></td>
|
||||
<td>
|
||||
<%= content_tag(:code, class: "path") { safe_join @model.path.split("/"), safe_join([tag.wbr, "/"]) } %>
|
||||
<% unless @model.contains_other_models? %>
|
||||
<div><%= button_tag(t(".organize.button_text"), class: "btn btn-warning btn-sm", "data-bs-toggle": "modal", "data-bs-target": "#confirm-move") if @model.needs_organizing? && policy(@model).edit? %></div>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= icon "tag", t(".tags") %></td>
|
||||
<td><%= render "tag_list", tags: @model.tags.order(taggings_count: :desc, name: :asc), filter: @filter %>
|
||||
<label for="toggle_tags"><i class="bi bi-info-circle"></i></label>
|
||||
<div class="toggle-box">
|
||||
<input type="checkbox" id="toggle_tags" hidden>
|
||||
<div class="hidden-tooltip" style="max-height:400px">
|
||||
<i class="bi bi-arrow-90deg-up"></i>
|
||||
<small>
|
||||
<%= render 'models/tags_info' %>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% if SiteSettings.social_enabled? %>
|
||||
<tr>
|
||||
<td><%= icon "people", t(".followers") %></td>
|
||||
<td><%= t("general.followers", count: @model.followers.count) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<tr>
|
||||
<td><%= render Components::AccessIndicator.new(object: @model, text: false) %></td>
|
||||
<td><%= render Components::AccessIndicator.new(object: @model, text: true, icon: false) %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<%= render Components::FollowButton.new(follower: current_user, target: @model) unless @model.remote? %>
|
||||
<%= render Components::GoButton.new(icon: "pencil", label: t("general.edit"), href: edit_model_path(@model), variant: "primary") if policy(@model).edit? %>
|
||||
<%= render Components::DoButton.new(icon: "trash", label: t("general.delete"), href: model_path(@model), method: :delete, variant: "outline-danger", confirm: translate("models.destroy.confirm")) if policy(@model).destroy? %>
|
||||
<% end %>
|
||||
|
||||
<div class="mb-3 w-100 text-center">
|
||||
<%= render Components::DownloadButton.new(model: @model) %>
|
||||
</div>
|
||||
|
||||
|
||||
<%= card :secondary, t("layouts.card_list_page.actions_heading") do %>
|
||||
<%= render Components::ReportButton.new(object: @model, path: new_model_report_path(@model)) %>
|
||||
<% end if SiteSettings.multiuser_enabled? %>
|
||||
|
||||
<% if !@model.parents.empty? && policy(@model).merge? %>
|
||||
<%= card :danger, t(".merge.heading") do %>
|
||||
<p>
|
||||
<%= t(".merge.warning") %>
|
||||
</p>
|
||||
<strong><%= t(".merge.with") %>:</strong>
|
||||
<% @model.parents.each do |parent| %>
|
||||
<%= render Components::DoButton.new(icon: "union", label: parent.name, href: merge_models_path(target: parent.public_id, models: [@model.public_id]), method: :post, variant: "danger") %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= render partial: "problem", collection: @model.problems.visible(problem_settings) %>
|
||||
|
||||
<%= card :secondary, t(".files_card.heading") do %>
|
||||
<a href="#files">Top</a>
|
||||
<ul>
|
||||
<% @groups.each_pair do |group, files| %>
|
||||
<li><a href="#<%= group.parameterize %>"><%= group.strip.careful_titleize %>*</a></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<%= link_to t(".files_card.bulk_edit"), bulk_edit_model_model_files_path(@model), class: "btn btn-secondary" if policy(@model).edit? %>
|
||||
<%= link_to t(".rescan"), scan_model_path(@model), class: "btn btn-warning", method: :post if policy(@model).scan? %>
|
||||
<% end %>
|
||||
|
||||
<%= render "links_card", links: @model.links %>
|
||||
|
||||
<div class="modal fade" id="confirm-move" data-bs-backdrop='static' tabindex="-1" aria-labelledby="confirmMoveLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title" id="confirmMoveLabel"><%= t(".organize.button_text") %></div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
<%= t(".organize.confirm.summary_html", from: @model.path, to: @model.formatted_path) %>
|
||||
</p>
|
||||
<p>
|
||||
<%= t(".organize.confirm.are_you_sure") %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><%= t(".organize.confirm.no") %></button>
|
||||
<%= button_to t(".organize.confirm.yes"), model_path(@model, "model[organize]": true), method: :patch, class: "btn btn-warning" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if policy(@model).upload? %>
|
||||
<%= card :warning, t(".upload_card.heading") do %>
|
||||
<%= form_with url: model_model_files_path(@model), id: "upload-form", class: "d-grid" do |form| %>
|
||||
|
||||
<%= content_tag :div, nil, class: "mb-3", data: {
|
||||
controller: "upload",
|
||||
action: "turbo:morph@window->upload#reconnect",
|
||||
max_file_size: SiteSettings.max_file_upload_size,
|
||||
allowed_file_types: input_accept_string,
|
||||
upload_endpoint: upload_path
|
||||
} %>
|
||||
<%= submit_tag translate(".submit"), class: "btn btn-primary d-block" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% content_for :head do %>
|
||||
<%= tag.meta property: "og:type", content: "website" %>
|
||||
<%= tag.meta property: "og:image", content: model_model_file_url(@model, @model.preview_file, format: @model.preview_file.extension) if @model.preview_file&.is_image? && !@model.sensitive %>
|
||||
<%= tag.meta name: "description", content: truncate(sanitize(@model.notes), length: 80) if @model.notes.present? %>
|
||||
<%= tag.meta name: "fediverse:creator", content: @model.creator.federails_actor.at_address if @model.creator %>
|
||||
<%= tag.link rel: "alternate", type: Mime[:oembed], href: model_url(@model, format: :oembed), title: @model.name %>
|
||||
<% end %>
|
||||
|
||||
<div itemscope itemtype="https://schema.org/3DModel">
|
||||
<%= turbo_stream_from @model %>
|
||||
<h1>
|
||||
<span itemprop="name">
|
||||
<a contenteditable="plaintext-only"
|
||||
data-controller="editable"
|
||||
data-action="focus->editable#onFocus blur->editable#onBlur"
|
||||
data-editable-field="model[name]"
|
||||
data-editable-path="<%= model_path(@model) %>"><%= @model.name %></a>
|
||||
</span>
|
||||
<%= link_to icon("search", t(".search")),
|
||||
"https://yeggi.com/q/#{ERB::Util.url_encode(@model.name)}/",
|
||||
class: "btn btn-outline", target: "manyfold_search", rel: "noreferrer" %>
|
||||
</h1>
|
||||
|
||||
<div class="row row-cols-md-2 mt-2">
|
||||
<div class="col-md-9" id="item_list">
|
||||
<% if @locked_files > 0 %>
|
||||
<div class="alert alert-info"><%= t(".preview", count: @locked_files) %></div>
|
||||
<% end %>
|
||||
|
||||
<%= render "image_carousel", images: @images %>
|
||||
|
||||
<%= card(:secondary) do %>
|
||||
<p class="card-text" itemprop="description"><%= markdownify @model.notes %></p>
|
||||
<% end if @model.notes.present? %>
|
||||
|
||||
<% if @num_files > 0 %>
|
||||
<h2><a name="files"><%= t(".files") %></a></h2>
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 mb-4">
|
||||
<%= render partial: "file", collection: @groups.delete(nil) %>
|
||||
</div>
|
||||
<% @groups.each_pair do |group, files| %>
|
||||
<h3><a name="<%= group.parameterize %>"><%= group.strip.careful_titleize %>*</a></h3>
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 mb-4">
|
||||
<%= render partial: "file", collection: files %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="col-md-3" id="sidebar">
|
||||
<%= card :secondary, t(".model_details") do %>
|
||||
<table class="table table-borderless table-sm">
|
||||
<% if SiteSettings.federation_enabled? %>
|
||||
<tr>
|
||||
<td>⁂</td>
|
||||
<td><% if @model.remote? %>
|
||||
<small><%= link_to @model.federails_actor.at_address, @model.federails_actor.profile_url, target: "new" %></small>
|
||||
<% else %>
|
||||
<small>
|
||||
<%= @model.federails_actor.short_at_address %>
|
||||
<%= render Components::CopyButton.new(text: @model.federails_actor.at_address) %>
|
||||
</small>
|
||||
<% end %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if @model.creator %>
|
||||
<tr>
|
||||
<td><%= icon "person", Creator.model_name.human %></td>
|
||||
<td><%= link_to @model.creator.name, @model.creator, itemprop: "author" %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<tr>
|
||||
<td>
|
||||
<i class="bi bi-file-zip" role="img"></i>
|
||||
</td>
|
||||
<td><%= link_to "zip archive", "/"+@model.library.name+"/"+@model.path.gsub("#","%23")+".zip" %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<i class="bi bi-controller" role="img"></i>
|
||||
</td>
|
||||
<td><%= link_to "Godot project", "/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/package_godot.zip" %></td>
|
||||
</tr>
|
||||
<% if @model.collection %>
|
||||
<tr>
|
||||
<td><%= icon "collection", Collection.model_name.human(count: 100) %></td>
|
||||
<td><%= link_to @model.collection.name, models_path({collection: @model.collection}.merge(@filter&.to_params)) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if SiteSettings.show_libraries || current_user&.is_administrator? %>
|
||||
<tr>
|
||||
<td><%= icon "boxes", Library.model_name.human %></td>
|
||||
<td><%= link_to @model.library.name, models_path({library: @model.library}.merge(@filter&.to_params)) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if @model.license %>
|
||||
<tr>
|
||||
<td><%= icon "card-heading", t(".license") %></td>
|
||||
<td>
|
||||
<%= Spdx.licenses[@model.license]&.fetch("reference") ?
|
||||
link_to(t_license(@model.license), Spdx.licenses[@model.license]["reference"], itemprop: "license") :
|
||||
t_license(@model.license) %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if @model.sensitive %>
|
||||
<tr>
|
||||
<td><%= icon("explicit", Model.human_attribute_name(:sensitive)) %></td>
|
||||
<td>
|
||||
<%= Model.human_attribute_name(:sensitive) %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<tr>
|
||||
<td><%= icon "folder", t(".path") %></td>
|
||||
<td>
|
||||
<%= content_tag(:code, class: "path") { safe_join @model.path.split("/"), safe_join([tag.wbr, "/"]) } %>
|
||||
<% unless @model.contains_other_models? %>
|
||||
<div><%= button_tag(t(".organize.button_text"), class: "btn btn-warning btn-sm", "data-bs-toggle": "modal", "data-bs-target": "#confirm-move") if @model.needs_organizing? && policy(@model).edit? %></div>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= icon "tag", t(".tags") %></td>
|
||||
<td><%= render "tag_list", tags: @model.tags.order(taggings_count: :desc, name: :asc), filter: @filter %></td>
|
||||
</tr>
|
||||
<% if SiteSettings.social_enabled? %>
|
||||
<tr>
|
||||
<td><%= icon "people", t(".followers") %></td>
|
||||
<td><%= t("general.followers", count: @model.followers.count) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<tr>
|
||||
<td><%= render Components::AccessIndicator.new(object: @model, text: false) %></td>
|
||||
<td><%= render Components::AccessIndicator.new(object: @model, text: true, icon: false) %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<%= render Components::FollowButton.new(follower: current_user, target: @model) unless @model.remote? %>
|
||||
<%= render Components::GoButton.new(icon: "pencil", label: t("general.edit"), href: edit_model_path(@model), variant: "primary") if policy(@model).edit? %>
|
||||
<%= render Components::DoButton.new(icon: "trash", label: t("general.delete"), href: model_path(@model), method: :delete, variant: "outline-danger", confirm: translate("models.destroy.confirm")) if policy(@model).destroy? %>
|
||||
<% end %>
|
||||
|
||||
<div class="mb-3 w-100 text-center">
|
||||
<%= render Components::DownloadButton.new(model: @model) %>
|
||||
</div>
|
||||
|
||||
<%= card :secondary, t("layouts.card_list_page.actions_heading") do %>
|
||||
<%= render Components::ReportButton.new(object: @model, path: new_model_report_path(@model)) %>
|
||||
<% end if SiteSettings.multiuser_enabled? %>
|
||||
|
||||
<% if !@model.parents.empty? && policy(@model).merge? %>
|
||||
<%= card :danger, t(".merge.heading") do %>
|
||||
<p>
|
||||
<%= t(".merge.warning") %>
|
||||
</p>
|
||||
<strong><%= t(".merge.with") %>:</strong>
|
||||
<% @model.parents.each do |parent| %>
|
||||
<%= render Components::DoButton.new(icon: "union", label: parent.name, href: merge_models_path(target: parent.public_id, models: [@model.public_id]), method: :post, variant: "danger") %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= render partial: "problem", collection: @model.problems.visible(problem_settings) %>
|
||||
|
||||
<%= card :secondary, t(".files_card.heading") do %>
|
||||
<a href="#files">Top</a>
|
||||
<ul>
|
||||
<% @groups.each_pair do |group, files| %>
|
||||
<li><a href="#<%= group.parameterize %>"><%= group.strip.careful_titleize %>*</a></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<%= link_to t(".files_card.bulk_edit"), bulk_edit_model_model_files_path(@model), class: "btn btn-secondary" if policy(@model).edit? %>
|
||||
<%= link_to t(".rescan"), scan_model_path(@model), class: "btn btn-warning", method: :post if policy(@model).scan? %>
|
||||
<% end %>
|
||||
|
||||
<%= render "links_card", links: @model.links %>
|
||||
|
||||
<div class="modal fade" id="confirm-move" data-bs-backdrop='static' tabindex="-1" aria-labelledby="confirmMoveLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title" id="confirmMoveLabel"><%= t(".organize.button_text") %></div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
<%= t(".organize.confirm.summary_html", from: @model.path, to: @model.formatted_path) %>
|
||||
</p>
|
||||
<p>
|
||||
<%= t(".organize.confirm.are_you_sure") %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><%= t(".organize.confirm.no") %></button>
|
||||
<%= button_to t(".organize.confirm.yes"), model_path(@model, "model[organize]": true), method: :patch, class: "btn btn-warning" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if policy(@model).upload? %>
|
||||
<%= card :warning, t(".upload_card.heading") do %>
|
||||
<%= form_with url: model_model_files_path(@model), id: "upload-form", class: "d-grid" do |form| %>
|
||||
|
||||
<%= content_tag :div, nil, class: "mb-3", data: {
|
||||
controller: "upload",
|
||||
action: "turbo:morph@window->upload#reconnect",
|
||||
max_file_size: SiteSettings.max_file_upload_size,
|
||||
allowed_file_types: input_accept_string,
|
||||
upload_endpoint: upload_path
|
||||
} %>
|
||||
<%= submit_tag translate(".submit"), class: "btn btn-primary d-block" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
if [ -f tmp/pids/server.pid ]; then
|
||||
rm tmp/pids/server.pid
|
||||
fi
|
||||
|
||||
echo "Preparing database..."
|
||||
bundle exec rails db:prepare:with_data
|
||||
|
||||
echo "run boot 'hook'"
|
||||
/manyfold/cli/manyfold.sh hook boot || true
|
||||
|
||||
echo "Setting database file ownership (SQLite3 only)..."
|
||||
bundle exec rake db:chown
|
||||
|
||||
echo "Cleaning up old cache files..."
|
||||
bundle exec rake tmp:cache:clear
|
||||
|
||||
echo "Setting temporary directory permissions..."
|
||||
chown -R $PUID:$PGID tmp log
|
||||
|
||||
echo "Launching application..."
|
||||
export RAILS_PORT=$PORT
|
||||
export RAILS_LOG_TO_STDOUT=true
|
||||
exec s6-setuidgid $PUID:$PGID "$@"
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
# always allow cors so remote XR viewers can load content
|
||||
Rails.application.config.middleware.insert_after Rack::Head, Rack::Cors do
|
||||
allow do
|
||||
origins '*'
|
||||
resource '*', headers: :any, methods: [:get, :options, :head]
|
||||
end
|
||||
end
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
# this calls the xrforge unix file-hooks in /root/hook.d/experience_updated (but in a safe way)
|
||||
# why: the database-write are sometimes chatty/duplicated.
|
||||
# therefore it ratelimits and prevents processing unchanged files.
|
||||
|
||||
require 'digest'
|
||||
|
||||
Rails.application.config.to_prepare do
|
||||
Model # Zeitwerk autoload model
|
||||
ModelFile
|
||||
|
||||
class ModelFile
|
||||
|
||||
# The macro is now run within the context of the existing Model class.
|
||||
after_save :run_cli_hooks
|
||||
|
||||
def run_cli_hooks
|
||||
|
||||
file = "#{self.model.library.path}/#{self.path_within_library()}"
|
||||
|
||||
if File.exist?(file) && (ENV['BOOT_SCAN'].present? || file.match?("datapackage") )
|
||||
cache_key = "ttl:file:cli_hook:#{self.id}#{Digest::MD5.file(file).hexdigest}"
|
||||
ttl = 60.0 # dont trigger hook twice for the same file within 60 seconds
|
||||
now = Time.current
|
||||
|
||||
# 1. Read the last run time from the shared cache
|
||||
last_run_time_str = Rails.cache.read(cache_key)
|
||||
last_run_time = last_run_time_str ? Time.parse(last_run_time_str) : nil
|
||||
|
||||
if last_run_time.nil? || (now - last_run_time) > ttl
|
||||
# 2. Write the new execution time to the shared cache
|
||||
# Use `write` to set the new time. Caching a string representation is often safer/easier.
|
||||
Rails.cache.write(cache_key, now.to_s, expires_in: ttl + 3.second)
|
||||
|
||||
puts "[app/config/initializers/xrforge.rb] running hook #{file}\n"
|
||||
Bundler.with_unbundled_env do
|
||||
#`TS_SLOTS=5 ts /manyfold/cli/manyfold.sh hook experience_updated #{file} &`
|
||||
`/manyfold/cli/manyfold.sh hook experience_updated #{file}`
|
||||
end
|
||||
Model.find(self.model.id).add_new_files_later(include_all_subfolders:false)
|
||||
else
|
||||
puts "[app/config/initializers/xrforge.rb] skipping hook\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
#class XRForgeRoutes
|
||||
# # Rack endpoints must implement a #call method that accepts the Rack environment hash (env).
|
||||
# def call(env)
|
||||
# # Wrap the environment in a Rails Request object for easier parameter access
|
||||
# request = ActionDispatch::Request.new(env)
|
||||
#
|
||||
# # 1. Access the model ID from the route parameters
|
||||
# # The route path parameters are stored in the routing information
|
||||
# model_id = request.path_parameters[:id]
|
||||
#
|
||||
# begin
|
||||
# # 2. Find the model
|
||||
# # Note: We are outside the controller, so we access Model directly.
|
||||
# model = Model.find(model_id)
|
||||
#
|
||||
# puts "-------------------------"
|
||||
# Rails.logger.info("build process for Model ID: #{model.id}")
|
||||
# url_helpers = Rails.application.routes.url_helpers
|
||||
# redirect_path = url_helpers.model_path(model)
|
||||
#
|
||||
# # 302 Found response for a temporary redirect
|
||||
# [
|
||||
# 302,
|
||||
# {
|
||||
# 'Content-Type' => 'text/html',
|
||||
# 'Location' => redirect_path
|
||||
# },
|
||||
# ['Build process started successfully.']
|
||||
# ]
|
||||
#
|
||||
# rescue ActiveRecord::RecordNotFound
|
||||
# # Handle model not found error
|
||||
# [
|
||||
# 404,
|
||||
# { 'Content-Type' => 'text/plain' },
|
||||
# ["Model with ID #{model_id} not found."]
|
||||
# ]
|
||||
# rescue => e
|
||||
# # Handle other errors
|
||||
# Rails.logger.error("Build Rack App Error: #{e.message}")
|
||||
# [
|
||||
# 500,
|
||||
# { 'Content-Type' => 'text/plain' },
|
||||
# ["Internal Server Error: Could not initiate build."]
|
||||
# ]
|
||||
# end
|
||||
# end
|
||||
#end
|
||||
#
|
||||
#Rails.configuration.to_prepare do
|
||||
# Rails.application.routes.draw do
|
||||
# resources :models, only: [] do
|
||||
# member do
|
||||
# # Point the route directly to an instance of the Rack application class.
|
||||
# # The route is: POST /models/:id/build
|
||||
# match 'build', to: XRForgeRoutes.new, via: :post
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#end
|
||||
|
|
@ -1,802 +0,0 @@
|
|||
---
|
||||
en:
|
||||
activerecord:
|
||||
attributes:
|
||||
collection:
|
||||
ai_indexable: Allow use for AI training
|
||||
caption: Caption
|
||||
collection: Parent Collection
|
||||
indexable: Allow search indexing
|
||||
models: Experiences
|
||||
name: Name
|
||||
notes: Description
|
||||
creator:
|
||||
ai_indexable: Allow use for AI training
|
||||
caption: Tagline
|
||||
indexable: Allow search indexing
|
||||
name: Creator Name
|
||||
notes: Description
|
||||
slug: Handle
|
||||
doorkeeper/application:
|
||||
access_token: Access Token
|
||||
confidential: Confidential
|
||||
created_at: Created
|
||||
name: Name
|
||||
owner: Owner
|
||||
redirect_uri: Redirect URI
|
||||
scopes: Scopes
|
||||
secret: Client Secret
|
||||
uid: Client ID
|
||||
federails/moderation/domain_block:
|
||||
created_at: Created at
|
||||
domain: Domain
|
||||
federails/moderation/report:
|
||||
content: Comment
|
||||
created_at: Received at
|
||||
federails_actor: Reported by
|
||||
object: Object
|
||||
library:
|
||||
caption: Caption
|
||||
create_path_if_not_on_disk: Auto-create folder
|
||||
default: Default
|
||||
icon: Icon
|
||||
name: Name
|
||||
notes: Notes
|
||||
path: Path
|
||||
s3_access_key_id: Access Key ID
|
||||
s3_bucket: Bucket Name
|
||||
s3_endpoint: Endpoint URL
|
||||
s3_path_style: Use path-style URLs
|
||||
s3_region: Region
|
||||
s3_secret_access_key: Secret Access Key
|
||||
storage_service: Storage Service
|
||||
tag_regex: Required Tags
|
||||
link:
|
||||
url: Link
|
||||
model:
|
||||
ai_indexable: Allow use for AI training
|
||||
caption: Caption
|
||||
collection: Collection
|
||||
collection_id: Collection
|
||||
creator: Creator
|
||||
creator_id: Creator
|
||||
images: Images
|
||||
indexable: Allow search indexing
|
||||
library_id: Library
|
||||
license: License
|
||||
model_files: Files
|
||||
name: Name
|
||||
notes: Description
|
||||
path: Path
|
||||
preview_file: Preview File
|
||||
sensitive: Sensitive Content
|
||||
tags: Tags
|
||||
model_file:
|
||||
caption: Caption
|
||||
digest: Digest
|
||||
filename: Filename
|
||||
model_id: Model
|
||||
notes: Notes
|
||||
presupported: Presupported
|
||||
presupported_version: Presupported version
|
||||
printed: Printed
|
||||
size: File Size
|
||||
unsupported_version: Unsupported version
|
||||
y_up: Y Up
|
||||
problem:
|
||||
category: Category
|
||||
ignored: Hidden
|
||||
note: Note
|
||||
problematic_type: Object Type
|
||||
severity: Severity
|
||||
user:
|
||||
approved: Account pending
|
||||
confirmation_sent_at: Confirmation sent at
|
||||
confirmation_token: Confirmation token
|
||||
confirmed_at: Confirmed at
|
||||
created_at: Created at
|
||||
current_password: Current password
|
||||
current_sign_in_at: Current sign in at
|
||||
current_sign_in_ip: Current sign in IP
|
||||
email: Email
|
||||
encrypted_password: Encrypted password
|
||||
failed_attempts: Failed attempts
|
||||
last_sign_in_at: Last sign in at
|
||||
last_sign_in_ip: Last sign in IP
|
||||
locked_at: Locked at
|
||||
password: Password
|
||||
password_confirmation: Confirm password
|
||||
remember_created_at: Remember created at
|
||||
remember_me: Remember me
|
||||
reset_password_sent_at: Reset password sent at
|
||||
reset_password_token: Reset password token
|
||||
sign_in_count: Sign in count
|
||||
unconfirmed_email: Unconfirmed email
|
||||
unlock_token: Unlock token
|
||||
updated_at: Updated at
|
||||
username: Account name
|
||||
errors:
|
||||
models:
|
||||
collection:
|
||||
attributes:
|
||||
collection:
|
||||
private: must be public
|
||||
creator:
|
||||
private: must be public
|
||||
doorkeeper/application:
|
||||
attributes:
|
||||
redirect_uri:
|
||||
forbidden_uri: is forbidden by the server.
|
||||
fragment_present: cannot contain a fragment.
|
||||
invalid_uri: must be a valid URI.
|
||||
relative_uri: must be an absolute URI.
|
||||
secured_uri: must be an HTTPS/SSL URI.
|
||||
unspecified_scheme: must specify a scheme.
|
||||
scopes:
|
||||
not_match_configured: doesn't match configured on the server.
|
||||
library:
|
||||
attributes:
|
||||
path:
|
||||
cannot_be_contained: cannot be inside another library
|
||||
cannot_contain: cannot contain other libraries
|
||||
non_writable: must be writable
|
||||
not_found: could not be found on disk
|
||||
unsafe: cannot be a privileged system path
|
||||
model:
|
||||
attributes:
|
||||
creator:
|
||||
private: must be public
|
||||
library:
|
||||
nested: can't be changed, model contains other models
|
||||
license:
|
||||
invalid_spdx: is not a valid license
|
||||
path:
|
||||
destination_exists: already exists
|
||||
nested: can't be changed, model contains other models
|
||||
model_file:
|
||||
attributes:
|
||||
filename:
|
||||
cannot_change_type: is not the same file type
|
||||
case_change_only: cannot be a case-only change
|
||||
presupported_version:
|
||||
already_presupported: cannot be set on a presupported file
|
||||
not_supported: is not a presupported file
|
||||
models:
|
||||
acts_as_taggable_on/tag:
|
||||
few: Tags
|
||||
many: Tags
|
||||
one: Tag
|
||||
other: Tags
|
||||
two: Tags
|
||||
zero: Tags
|
||||
collection:
|
||||
few: Collections
|
||||
many: Collections
|
||||
one: Collection
|
||||
other: Collections
|
||||
two: Collections
|
||||
zero: Collections
|
||||
creator:
|
||||
few: Creators
|
||||
many: Creators
|
||||
one: Creator
|
||||
other: Creators
|
||||
two: Creators
|
||||
zero: Creators
|
||||
federails/moderation/domain_block:
|
||||
few: Domain Blocks
|
||||
many: Domain Blocks
|
||||
one: Domain Block
|
||||
other: Domain Blocks
|
||||
two: Domain Blocks
|
||||
zero: Domain Blocks
|
||||
federails/moderation/report:
|
||||
few: Reports
|
||||
many: Reports
|
||||
one: Report
|
||||
other: Reports
|
||||
two: Reports
|
||||
zero: Reports
|
||||
library:
|
||||
few: Libraries
|
||||
many: Libraries
|
||||
one: Library
|
||||
other: Libraries
|
||||
two: Libraries
|
||||
zero: Libraries
|
||||
link:
|
||||
few: Links
|
||||
many: Links
|
||||
one: Link
|
||||
other: Links
|
||||
two: Links
|
||||
zero: Links
|
||||
model:
|
||||
few: Experiences
|
||||
many: Experiences
|
||||
one: Experience
|
||||
other: Experiences
|
||||
two: Experiences
|
||||
zero: Experiences
|
||||
model_file:
|
||||
few: Files
|
||||
many: Files
|
||||
one: File
|
||||
other: Files
|
||||
two: Files
|
||||
zero: Files
|
||||
problem:
|
||||
few: Problems
|
||||
many: Problems
|
||||
one: Problem
|
||||
other: Problems
|
||||
two: Problems
|
||||
zero: Problems
|
||||
user:
|
||||
few: Accounts
|
||||
many: Accounts
|
||||
one: Account
|
||||
other: Accounts
|
||||
two: Accounts
|
||||
zero: Accounts
|
||||
activity:
|
||||
index:
|
||||
description: Entries are discard after %{retention_period}.
|
||||
message: Message
|
||||
name: Name
|
||||
time: When
|
||||
title: Recent Activity
|
||||
activity_helper:
|
||||
status_icon:
|
||||
completed: Complete
|
||||
error: Errored
|
||||
queued: Queued
|
||||
working: Working
|
||||
application:
|
||||
caber_relation_fields:
|
||||
delete: Delete
|
||||
permissions:
|
||||
edit: Can edit
|
||||
own: Owner (can view, edit, delete, and share)
|
||||
preview: 'Preview: specific previewable files only'
|
||||
view: View only
|
||||
subject:
|
||||
placeholder: Email address, account name, or role
|
||||
role:
|
||||
member: Any logged-in local account
|
||||
public: Everyone (without login)
|
||||
you: "(you)"
|
||||
caber_relations_form:
|
||||
add: add another permission
|
||||
permissions: Sharing
|
||||
demo_mode: This instance is in demo mode. You cannot add or remove models, but you can do everything else.
|
||||
filters_card:
|
||||
missing_tags: Missing tags
|
||||
remove_collection_filter: Remove collection filter
|
||||
remove_creator_filter: Remove creator filter
|
||||
remove_library_filter: Remove library filter
|
||||
remove_missing_tag_filter: Remove missing tag filter
|
||||
remove_search_filter: Remove search filter
|
||||
remove_tag_filter: Remove tag filter
|
||||
search: Search
|
||||
title: Filters
|
||||
unknown: Unknown
|
||||
footer:
|
||||
about: About this instance
|
||||
api: Explore our API
|
||||
by_html: Designed and built by <a href="https://floppy.org.uk" target="_blank" rel="noreferrer">James</a> with help from <a href="https://github.com/manyfold3d/manyfold/graphs/contributors" target="_blank" rel="noreferrer">our contributors</a>.
|
||||
community: Join the community
|
||||
instance_heading: Instance Details
|
||||
issues: Report a problem
|
||||
open_source_html: <a href="https://github.com/manyfold3d/manyfold" target="_blank" rel="noreferrer">Open Source</a> under the <a href="https://github.com/manyfold3d/manyfold/blob/main/LICENSE.md" target="_blank" rel="noreferrer" rel="license">MIT license</a>.
|
||||
powered_by_html: Powered by <a href="https://forgejo.isvery.ninja/coderofsalvation/xrforge">XR Forge</a>, <a href="https://manifold.app" target="_blank">Manyfold</a>, <a href="https://xrfragment.org">XR Fragments</a> and <a href="https://nixos.org" target="_blank">NIX</a>
|
||||
sponsor: Sponsor development
|
||||
support: Support this instance
|
||||
version: Version
|
||||
link_fields:
|
||||
url:
|
||||
delete: Delete
|
||||
placeholder: Any related web page
|
||||
links_form:
|
||||
add: add another link
|
||||
navbar:
|
||||
account: My Settings
|
||||
activity: Activity
|
||||
check_existing: Rescan all models
|
||||
check_results: Rescan filtered models
|
||||
home: Homepage
|
||||
log_in: Sign in
|
||||
log_out: Sign out
|
||||
moderator_settings: Moderator Settings
|
||||
navbar:
|
||||
toggler:
|
||||
label: Toggle navigation
|
||||
scan: Scan
|
||||
scan_changes: Scan for new files
|
||||
scanning: Scanning
|
||||
search: Search
|
||||
settings: Site Settings
|
||||
upload: Upload
|
||||
order_buttons:
|
||||
sort:
|
||||
name: Sort by Name
|
||||
time: Sort by Time
|
||||
search_error: Error in search syntax. Please check and try again!
|
||||
tag_list:
|
||||
unrelated_tag_count:
|
||||
one: "%{count} unrelated tag hidden"
|
||||
other: "%{count} unrelated tags hidden"
|
||||
tagline: Helping you keep track of your 3d print files
|
||||
tags_card:
|
||||
skip_tags: Skip tag list
|
||||
title: xrforge
|
||||
application_helper:
|
||||
ai_indexable_select_options:
|
||||
always_no: Always no
|
||||
always_yes: Always yes
|
||||
inherit: Inherit from parent object or default site setting; currently '%{inherited}'
|
||||
indexable_select_options:
|
||||
always_no: Always no
|
||||
always_yes: Always yes
|
||||
inherit: Inherit from parent object or default site setting; currently '%{inherited}'
|
||||
'no': 'No'
|
||||
'yes': 'Yes'
|
||||
components:
|
||||
altcha_widget:
|
||||
help: privacy-friendly spam protection by ALTCHA
|
||||
copy_button:
|
||||
copy: Copy to Clipboard
|
||||
display_user_quota:
|
||||
request_increase: To request a quota increase, contact your site administrator.
|
||||
download_button:
|
||||
download:
|
||||
missing: Request download
|
||||
preparing: Preparing download, please wait
|
||||
ready: Ready to download
|
||||
file_type: "%{type} Files Only"
|
||||
label: Download All
|
||||
menu_header: Download Options
|
||||
supported: Supported Files Only
|
||||
unsupported: Unsupported Files Only
|
||||
follow_button:
|
||||
follow: Follow %{name}
|
||||
pending: Requested
|
||||
unfollow: Unfollow %{name}
|
||||
link_list:
|
||||
sync: Synchronize
|
||||
modal:
|
||||
close: Close
|
||||
model_card:
|
||||
delete_button:
|
||||
label: Delete model %{name}
|
||||
text: Delete
|
||||
edit_button:
|
||||
label: Edit model %{name}
|
||||
text: Edit
|
||||
no_preview: No preview available
|
||||
open_button:
|
||||
label: Open model %{name}
|
||||
text: Open
|
||||
search_help:
|
||||
boolean: Use "or" to find models that match any of the terms.
|
||||
federation: Search for any Fediverse username to follow it.
|
||||
filename: You can search within filenames by explicitly specifying the field.
|
||||
intro: 'Find what you need with our powerful search syntax:'
|
||||
more_details_html: For more information, read the full documentation for <a href="https://github.com/wvanbergen/scoped_search/wiki/Query-language">scoped_search's query language</a>.
|
||||
negation: To exclude terms, use "not", "!", or "-".
|
||||
parentheses: Group terms with parentheses for more complex logic combinations.
|
||||
path: Search within model folder paths by explicitly specifying it; use `~` for a partial match.
|
||||
quotes: To look for multiple words in a single term, use quotes; only models with the exact text will be shown.
|
||||
simple: By default, search will find models that match all terms.
|
||||
specific_fields: You can look for terms in a few specific fields. Use "~" to match part of the field; "=" will try to match the whole thing. Model descriptions and library names are only searched if you explicitly specify the fields.
|
||||
tag: Finds models with a specific tag
|
||||
title: Search Syntax
|
||||
unset: Use "set?" to query if a particular field is set, and add "not" to find the opposite.
|
||||
without_tag: Use "!=" to find models without a certain tag
|
||||
concerns:
|
||||
linkable:
|
||||
sync:
|
||||
bad_request: 'Synchronization failed: missing link ID'
|
||||
success: Synchronization requested successfully
|
||||
doorkeeper:
|
||||
applications:
|
||||
buttons:
|
||||
authorize: Authorize
|
||||
cancel: Cancel
|
||||
destroy: Destroy
|
||||
edit: Edit
|
||||
submit: Submit
|
||||
confirmations:
|
||||
destroy: Are you sure?
|
||||
edit:
|
||||
title: Edit application
|
||||
form:
|
||||
error: Whoops! Check your form for possible errors
|
||||
help:
|
||||
blank_redirect_uri: Leave it blank if you configured your provider to use Client Credentials, Resource Owner Password Credentials or any other grant type that doesn't require redirect URI.
|
||||
confidential: Application will be used where the client secret can be kept confidential. Native mobile apps and Single Page Apps are considered non-confidential.
|
||||
redirect_uri: Use one line per URI
|
||||
scopes: Separate scopes with spaces. Leave blank to use the default scopes.
|
||||
index:
|
||||
actions: Actions
|
||||
callback_url: Callback URL
|
||||
confidential: Confidential?
|
||||
confidentiality:
|
||||
'no': 'No'
|
||||
'yes': 'Yes'
|
||||
name: Name
|
||||
new: New Application
|
||||
title: Your applications
|
||||
new:
|
||||
title: New Application
|
||||
show:
|
||||
actions: Actions
|
||||
application_id: UID
|
||||
callback_urls: Callback urls
|
||||
confidential: Confidential
|
||||
not_defined: Not defined
|
||||
scopes: Scopes
|
||||
secret: Secret
|
||||
secret_hashed: Secret hashed
|
||||
title: 'Application: %{name}'
|
||||
authorizations:
|
||||
buttons:
|
||||
authorize: Authorize
|
||||
deny: Deny
|
||||
error:
|
||||
title: An error has occurred
|
||||
form_post:
|
||||
title: Submit this form
|
||||
new:
|
||||
able_to: This application will be able to
|
||||
prompt: Authorize %{client_name} to use your account?
|
||||
title: Authorization required
|
||||
show:
|
||||
title: Authorization code
|
||||
authorized_applications:
|
||||
buttons:
|
||||
revoke: Revoke
|
||||
confirmations:
|
||||
revoke: Are you sure?
|
||||
index:
|
||||
application: Application
|
||||
created_at: Created At
|
||||
date_format: "%Y-%m-%d %H:%M:%S"
|
||||
title: Your authorized applications
|
||||
errors:
|
||||
messages:
|
||||
access_denied: The resource owner or authorization server denied the request.
|
||||
admin_authenticator_not_configured: Access to admin panel is forbidden due to Doorkeeper.configure.admin_authenticator being unconfigured.
|
||||
credential_flow_not_configured: Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.
|
||||
forbidden_token:
|
||||
missing_scope: Access to this resource requires scope "%{oauth_scopes}".
|
||||
invalid_client: Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.
|
||||
invalid_code_challenge_method:
|
||||
one: The code_challenge_method must be %{challenge_methods}.
|
||||
other: The code_challenge_method must be one of %{challenge_methods}.
|
||||
zero: The authorization server does not support PKCE as there are no accepted code_challenge_method values.
|
||||
invalid_grant: The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
|
||||
invalid_redirect_uri: The requested redirect uri is malformed or doesn't match client redirect URI.
|
||||
invalid_request:
|
||||
invalid_code_challenge: Code challenge is required.
|
||||
missing_param: 'Missing required parameter: %{value}.'
|
||||
request_not_authorized: Request need to be authorized. Required parameter for authorizing request is missing or invalid.
|
||||
unknown: The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.
|
||||
invalid_scope: The requested scope is invalid, unknown, or malformed.
|
||||
invalid_token:
|
||||
expired: The access token expired
|
||||
revoked: The access token was revoked
|
||||
unknown: The access token is invalid
|
||||
resource_owner_authenticator_not_configured: Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfigured.
|
||||
revoke:
|
||||
unauthorized: You are not authorized to revoke this token
|
||||
server_error: The authorization server encountered an unexpected condition which prevented it from fulfilling the request.
|
||||
temporarily_unavailable: The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.
|
||||
unauthorized_client: The client is not authorized to perform this request using this method.
|
||||
unsupported_grant_type: The authorization grant type is not supported by the authorization server.
|
||||
unsupported_response_mode: The authorization server does not support this response mode.
|
||||
unsupported_response_type: The authorization server does not support this response type.
|
||||
flash:
|
||||
applications:
|
||||
create:
|
||||
notice: Application created.
|
||||
destroy:
|
||||
notice: Application deleted.
|
||||
update:
|
||||
notice: Application updated.
|
||||
authorized_applications:
|
||||
destroy:
|
||||
notice: Application revoked.
|
||||
layouts:
|
||||
admin:
|
||||
nav:
|
||||
applications: Applications
|
||||
home: Home
|
||||
oauth2_provider: OAuth2 Provider
|
||||
title: Doorkeeper
|
||||
application:
|
||||
title: OAuth authorization required
|
||||
pre_authorization:
|
||||
status: Pre-authorization
|
||||
doorkeeper_applications:
|
||||
create:
|
||||
failure: An error occurred, and the application could not be created.
|
||||
success: Application created successfully.
|
||||
destroy:
|
||||
success: Application deleted successfully.
|
||||
edit:
|
||||
title: Edit application
|
||||
form:
|
||||
confidential:
|
||||
help: A confidential application can hold secrets securely (e.g. a web server backend, or machine-to-machine script).
|
||||
redirect_uri:
|
||||
help: Use "urn:ietf:wg:oauth:2.0:oob" if your application does not need a redirect URI (e.g. machine-to-machine apps).
|
||||
scopes:
|
||||
label: Scopes
|
||||
submit: Save application
|
||||
index:
|
||||
description: OAuth applications allow you to access Manyfold resources from other services via our API.
|
||||
new: New application
|
||||
title: OAuth Applications
|
||||
new:
|
||||
title: New application
|
||||
show:
|
||||
destroy: Delete
|
||||
edit: Edit
|
||||
title: Application details
|
||||
update:
|
||||
failure: An error occurred, and the application could not be saved.
|
||||
success: Application saved successfully.
|
||||
errors:
|
||||
messages:
|
||||
already_confirmed: was already confirmed, please try signing in
|
||||
confirmation_period_expired: needs to be confirmed within %{period}, please request a new one
|
||||
expired: has expired, please request a new one
|
||||
not_found: not found
|
||||
not_locked: was not locked
|
||||
not_saved:
|
||||
one: '1 error prohibited this %{resource} from being saved:'
|
||||
other: "%{count} errors prohibited this %{resource} from being saved:"
|
||||
weak_password: not strong enough. Consider adding a number, symbols or more letters to make it stronger.
|
||||
follows:
|
||||
actor_table:
|
||||
actions: Actions
|
||||
address: Fediverse Address
|
||||
name: Name
|
||||
non_manyfold_account: This is not a Manyfold account; you can follow it, but probably nothing interesting will happen, at least for now.
|
||||
follow_remote_actor:
|
||||
followed: Followed %{actor} successfully
|
||||
index:
|
||||
followers: Followers
|
||||
following: Following
|
||||
title: Connections
|
||||
new:
|
||||
help: You can follow public creators, collections or models on another Manyfold server, in fact any public account in the Fediverse! Just enter the account name in the search box!
|
||||
no_results: Sorry, couldn't find anything for "%{query}". Is it a valid ActivityPub account or URL?
|
||||
results: Search Results
|
||||
title: Follow the Fediverse
|
||||
remote_follow:
|
||||
help: You don't need an account on this server to follow %{name}; enter your own account name here, and we'll send you home to complete the process.
|
||||
no_results_html: We couldn't find your home account; did you enter it correctly?
|
||||
placeholder: Your Fediverse handle, e.g. @manyfold@3dp.chat
|
||||
submit: Take me home
|
||||
title: Follow %{name}
|
||||
search_form:
|
||||
placeholder: Enter a Fediverse account or URL, e.g. @admin@try.manyfold.app
|
||||
submit: Search
|
||||
unfollow_remote_actor:
|
||||
unfollowed: Unfollowed %{actor}
|
||||
general:
|
||||
delete: Delete
|
||||
download: Download
|
||||
edit: Edit
|
||||
expand: Expand
|
||||
followers:
|
||||
few: "%{count} Followers"
|
||||
many: "%{count} Followers"
|
||||
one: "%{count} Follower"
|
||||
other: "%{count} Followers"
|
||||
two: "%{count} Followers"
|
||||
zero: "%{count} Followers"
|
||||
menu: Menu
|
||||
new: New
|
||||
private: Private
|
||||
public: Publicly visible
|
||||
report: Report %{type}
|
||||
save: Save
|
||||
shared: Shared with local users
|
||||
view: View
|
||||
home:
|
||||
activity:
|
||||
created: added %{time} ago
|
||||
updated: updated %{time} ago
|
||||
browsing:
|
||||
content: You can explore models by clicking the links in the menu bar; browse a complete list and filter by tag, or browse by collection or creator. Alternatively just type into the search box to find what you want!
|
||||
manual_link: User guide
|
||||
more_access: Currently you have read-only access to this instance; to get more permissions, such as uploading, contact your instance administrator.
|
||||
title: Browsing
|
||||
federation:
|
||||
content_html: This Manyfold instance is part of the <a href="https://jointhefediverse.net">Fediverse</a>, a network of social media sites that all work together. That means that if you have an account here, you can follow content on other Manyfold instances, or people can follow your content from other platforms like Mastodon.
|
||||
creator_handle_html: 'The fediverse handle of your creator profile is: <code>%{handle}</code>.'
|
||||
following: If you know the handle of someone or something you want to follow, just enter it in the search box; otherwise, enter your personal handle above when you follow something on another instance.
|
||||
handle_html: 'Your fediverse handle is: <code>%{handle}</code>'
|
||||
title: Federation
|
||||
index:
|
||||
no_activities: There are no activities to display for now.
|
||||
open_search_help: Search syntax
|
||||
recent_activity: Recent Activity
|
||||
search:
|
||||
placeholder: What are you looking for?
|
||||
submit: Search
|
||||
publishing:
|
||||
content: You can publish content publicly by giving "view" or "preview" permission to the "public" role on the item's edit page. Creators for public models will automatically be made public, but collections need to be expicitly published if you want them to be visible.
|
||||
existing_creator:
|
||||
button: Edit your creator profile
|
||||
content: 'If you''re publishing your own work, you will probably want to customise your creator profile:'
|
||||
new_creator:
|
||||
button: Set up a new creator profile
|
||||
content: 'If you''re publishing your own work, you will probably want to set up your own creator profile:'
|
||||
title: Publishing
|
||||
support:
|
||||
content: Manyfold instances are run by people like you! If you find this instance useful, you can help keep it running by clicking below.
|
||||
manyfold_html: To support development of the Manyfold software itself, you can do so at <a href="https://opencollective.com/manyfold">OpenCollective</a>.
|
||||
support_link: Support this instance
|
||||
title: Support
|
||||
uploading:
|
||||
how_to_upload: You can add models by clicking the upload button in the menu bar. To upload lots of files as a single model, compress them in a single archive file (e.g. ZIP or RAR).
|
||||
permissions:
|
||||
edit: You can grant additional permissions on the item's edit page.
|
||||
member: By default, uploaded content will be visible to any local logged-in user.
|
||||
private: By default, uploaded content will not be visible to any other users.
|
||||
quota: You can upload up to %{quota} of content, and you can always view your current quota usage on your settings page.
|
||||
title: Uploading
|
||||
upload: Upload
|
||||
welcome:
|
||||
lead: This site is running Manyfold, software for managing and sharing 3D models; here's a quick guide...
|
||||
title: Welcome to %{site_name}!
|
||||
imports:
|
||||
create:
|
||||
success: Imported requested; the results should appear shortly.
|
||||
new:
|
||||
description: From some sites, Manyfold can download models for you with just a link!
|
||||
heading: Import from a link
|
||||
import: Import this link
|
||||
import_type_html: "<code>%{url}</code> will be added as a new %{object_type}. The following data can be imported automatically:"
|
||||
jobs:
|
||||
activity:
|
||||
collection_published:
|
||||
comment: A new collection of 3D models, ["%{name}"](%{url}), was just published!
|
||||
model_collected:
|
||||
comment: '["%{model_name}"](%{model_url}) was just added to the ["%{collection_name}"](%{collection_url}) collection.'
|
||||
model_published:
|
||||
comment: A new 3D model, ["%{name}"](%{url}), was just published!
|
||||
updated_model:
|
||||
comment: The 3D model ["%{name}"](%{url}), was just updated!
|
||||
analysis:
|
||||
analyse_model_file:
|
||||
detect_duplicates: Detecting duplicate files
|
||||
detect_ineffiency: Detecting inefficient formats
|
||||
file_statistics: Calculating file statistics
|
||||
matching: Matching supported files
|
||||
file_conversion:
|
||||
exporting: Exporting new file
|
||||
loading_mesh: Loading mesh
|
||||
geometric_analysis:
|
||||
direction_check: Checking surface orientation
|
||||
loading_mesh: Loading mesh
|
||||
manifold_check: Checking that mesh is manifold
|
||||
scan:
|
||||
detect_filesystem_changes:
|
||||
building_filename_list: Building file list
|
||||
building_folder_list: Building changed folder list
|
||||
creating_models: Creating models
|
||||
kaminari:
|
||||
first_page:
|
||||
label: Go to first page
|
||||
last_page:
|
||||
label: Go to last page
|
||||
next_page:
|
||||
label: Go to next page
|
||||
page:
|
||||
current_page: Current page
|
||||
label: Go to page %{page}
|
||||
paginator:
|
||||
label: Page navigation
|
||||
prev_page:
|
||||
label: Go to previous page
|
||||
layouts:
|
||||
application:
|
||||
alert:
|
||||
danger: Danger
|
||||
info: Info
|
||||
skip_to_content: Skip to main content
|
||||
card_list_page:
|
||||
actions_heading: Actions
|
||||
settings:
|
||||
activeadmin: Advanced Administration
|
||||
appearance: Appearance
|
||||
downloads: Downloads
|
||||
libraries: Libraries
|
||||
moderation_settings_title: Moderation Settings
|
||||
organization: Organization
|
||||
performance: Performance Dashboard
|
||||
pghero: PgHero
|
||||
sidekiq: Sidekiq
|
||||
site_settings_title: Site Settings
|
||||
tools_heading: Advanced Tools
|
||||
licenses:
|
||||
0BSD: BSD Zero Clause License
|
||||
CC-BY-40: Creative Commons Attribution
|
||||
CC-BY-NC-40: Creative Commons Attribution NonCommercial
|
||||
CC-BY-NC-ND-40: Creative Commons Attribution NonCommercial NoDerivatives
|
||||
CC-BY-NC-SA-40: Creative Commons Attribution NonCommercial ShareAlike
|
||||
CC-BY-ND-40: Creative Commons Attribution NoDerivatives
|
||||
CC-BY-SA-40: Creative Commons Attribution ShareAlike
|
||||
CC-PDDC: Creative Commons Public Domain Declaration
|
||||
CC0-10: Creative Commons Zero
|
||||
GPL-20-only: GNU General Public License v2.0
|
||||
GPL-30-only: GNU General Public License v3.0
|
||||
LGPL-20-only: GNU Lesser General Public License v2
|
||||
LGPL-30-only: GNU Lesser General Public License v3
|
||||
LicenseRef-Commercial: Commercial; private use only
|
||||
MIT: MIT
|
||||
moderator_mailer:
|
||||
new_approval:
|
||||
greeting: Hi!
|
||||
message: Someone new has signed up for an account, and requires approval. Approve the account at %{link}
|
||||
subject: New account needs approval
|
||||
new_report:
|
||||
greeting: Hi!
|
||||
message: Someone has reported content which needs moderations. Review the report at %{link}
|
||||
subject: New report received
|
||||
renderer:
|
||||
errors:
|
||||
canvas: 'Could not find #webgl canvas!'
|
||||
load: Load Error
|
||||
webglrenderer: Could not create renderer!
|
||||
load: Load
|
||||
processing: Reticulating splines...
|
||||
reports:
|
||||
create:
|
||||
success: Report submitted. Thank you!
|
||||
new:
|
||||
description: If this item violates any laws or server policies, you can report it to our moderators. Add a comment to let us know why!
|
||||
submit: Send report
|
||||
title: 'Report %{type}: "%{name}"'
|
||||
scans:
|
||||
create:
|
||||
success: Scan started.
|
||||
security:
|
||||
running_as_root_html: Manyfold is running as root, which is a security risk. Run as a different system user by setting the <code>PUID</code> and <code>PGID</code> environment variables. See <a href='https://manyfold.app/sysadmin/configuration.html#required'>the configuration documentation</a> for details.
|
||||
sites:
|
||||
cgtrader: CGTrader
|
||||
comicsgamesandthings: Comics, Games, and Things
|
||||
cults3d: Cults3D
|
||||
github: GitHub
|
||||
makerworld: MakerWorld
|
||||
manyfold: Manyfold
|
||||
myminifactory: MyMiniFactory
|
||||
printables: Printables
|
||||
thangs: Thangs
|
||||
theminiindex: The Mini Index
|
||||
thingiverse: Thingiverse
|
||||
yeggi: yeggi
|
||||
user_mailer:
|
||||
account_approved:
|
||||
greeting: Hi!
|
||||
message: Your account has been approved; you may now sign in at %{link}
|
||||
subject: Account approved
|
||||
test_email:
|
||||
subject: Test email
|
||||
test_email_message: Test email
|
||||
users:
|
||||
registrations:
|
||||
create:
|
||||
altcha_failed: ALTCHA verification failed
|
||||
views:
|
||||
pagination:
|
||||
first: "« First"
|
||||
last: Last »
|
||||
next: Next ›
|
||||
previous: "‹ Prev"
|
||||
truncate: "…"
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
---
|
||||
en:
|
||||
models:
|
||||
bulk_edit:
|
||||
description: 'Select models to change:'
|
||||
form_subtitle: 'Select changes to make:'
|
||||
merge: Merge selected models
|
||||
needs_organizing: Needs organizing
|
||||
remove_tags: Remove tags
|
||||
select: Select model '%{name}'
|
||||
select_all: Select all models
|
||||
submit: Update Selected Experiences
|
||||
title: Bulk Edit Experiences
|
||||
update_all: Update All %{count} Experiences
|
||||
bulk_fields:
|
||||
add_tags: Add tags
|
||||
bulk_update:
|
||||
success: Experiences updated successfully.
|
||||
configure_merge:
|
||||
common_root:
|
||||
description: The models will be combined into a single one in the shared root folder
|
||||
title: New model in common root folder
|
||||
description: Select one of the models to merge the others into, or create a new one.
|
||||
heading: Merge models
|
||||
new_model:
|
||||
description: A new model will be created from the combined data, and automatically organised on disk.
|
||||
title: New model
|
||||
create:
|
||||
success: File(s) uploaded successfully.
|
||||
destroy:
|
||||
confirm: This will delete associated files if they exist on disk. Are you sure you want to continue?
|
||||
success: Model deleted!
|
||||
file:
|
||||
delete: Delete file
|
||||
edit: Edit file
|
||||
open_button:
|
||||
label: View details for %{name}
|
||||
text: Open
|
||||
presupported: Presupported Version
|
||||
set_as_preview: Set as preview
|
||||
form:
|
||||
notes:
|
||||
help_html: You can use <a href="https://www.markdownguide.org/cheat-sheet/" target="markdown">Markdown</a>.
|
||||
preview_file:
|
||||
help: The file displayed as a model preview in library pages
|
||||
tags: Tags
|
||||
general:
|
||||
edit: Edit Model
|
||||
image_carousel:
|
||||
next: Next
|
||||
play_pause: Play or pause images
|
||||
previous: Previous
|
||||
select_slide: Choose image to display
|
||||
slide_label: "%{name} (%{index} of %{count})"
|
||||
list:
|
||||
bulk_edit: Edit All Experiences
|
||||
no_results_html: Sorry, we couldn't find anything to show you! Try changing your filters or search terms, or uploading some models.
|
||||
no_results_signed_out_html: Sorry, we couldn't find anything to show you! There might be more to see if you <a href="%{link}">sign in</a>.
|
||||
skip_models: Skip model list
|
||||
merge:
|
||||
success: Experiences merged successfully.
|
||||
new:
|
||||
description: Add new models by uploading files! If you upload a compressed archive, it will be extracted and become a single model containing all the files. If you upload individual files, they will each become a separate model.
|
||||
files:
|
||||
label: Select Files
|
||||
free_space: "(%{available} free)"
|
||||
library:
|
||||
help: The library to upload to.
|
||||
submit: Create models
|
||||
title: Upload
|
||||
problem:
|
||||
merge_all: Merge all
|
||||
scan:
|
||||
success: Model scan started
|
||||
show:
|
||||
download_preparing: Download is being prepared, please wait.
|
||||
download_requested: Download requested and will be ready soon, please wait.
|
||||
files: Files
|
||||
files_card:
|
||||
bulk_edit: Edit all files
|
||||
heading: Files
|
||||
followers: Followers
|
||||
license: License
|
||||
merge:
|
||||
heading: Merge
|
||||
warning: Merging moves all files from this model to the target, and removes this model. File metadata is preserved, but any model metadata will be lost!
|
||||
with: Merge with
|
||||
model_details: Model Details
|
||||
organize:
|
||||
button_text: Organize files
|
||||
confirm:
|
||||
are_you_sure: Are you sure you want to do this?
|
||||
'no': No, cancel
|
||||
summary_html: The folder and files that make up this model will be moved from:<br> <code>%{from}</code><br> to<br> <code>%{to}</code>
|
||||
'yes': Yes, move the files
|
||||
path: Path
|
||||
preview: This is just a preview of the complete model, which contains %{count} more files. Contact the model owner to get full access.
|
||||
rescan: Rescan files
|
||||
search: Search the Internet for models with this name
|
||||
submit: Upload Files
|
||||
tags: Tags
|
||||
upload_card:
|
||||
heading: Upload
|
||||
update:
|
||||
success: Model details saved.
|
||||
|
Before Width: | Height: | Size: 69 KiB |
|
|
@ -1,23 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
width="500.44669"
|
||||
height="526.20935"
|
||||
id="svg46"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs46" />
|
||||
<path
|
||||
d="m 355.95692,38.225006 c 2.73479,2.461154 5.39209,4.994684 8.01758,7.571777 1.34542,1.316746 2.71389,2.609828 4.08398,3.900879 6.87186,6.601735 12.61753,13.935978 18.35938,21.511719 1.13437,-0.639375 2.26875,-1.27875 3.4375,-1.9375 8.27553,-4.036494 14.6524,-3.37541 23.29687,-0.410156 20.96255,8.650811 34.56671,29.342441 43.21143,49.559575 11.18602,27.52311 17.36113,56.91586 12.51514,86.51855 -2.69159,19.09202 5.52731,31.81769 15.36645,47.34644 17.668,27.88651 17.668,27.88651 14.77417,41.84497 -2.16162,7.24282 -7.64806,11.92453 -13.60156,16.26562 -8.45868,6.85964 -13.04155,18.66184 -14.44947,29.16578 -0.37215,3.75471 -0.68688,7.51068 -0.98803,11.27172 -1.87048,20.90882 -5.32469,40.51095 -21.5625,55.375 -2.85414,2.34651 -5.75482,4.23758 -9,6 -0.82371,0.45118 -1.64742,0.90235 -2.4961,1.36719 -16.13456,7.73544 -35.26629,7.046 -52.77587,7.16699 -10.30918,-0.0912 -10.30918,-0.0912 -19.72803,3.46582 -3.4632,5.12978 -5.11284,11.14152 -6.69922,17.05469 -0.48693,1.75394 -0.974,3.50785 -1.46118,5.26172 -0.24896,0.90766 -0.49792,1.81532 -0.75443,2.75049 -7.2809,26.46075 -16.04317,51.78931 -27.08517,76.9331 -5.72933,-0.6399 -10.95971,-2.06834 -16.44922,-3.80078 -0.89387,-0.27764 -1.78774,-0.55529 -2.7087,-0.84135 -2.84477,-0.88514 -5.68714,-1.77777 -8.52958,-2.67037 -1.88983,-0.58835 -3.7798,-1.17624 -5.66992,-1.76367 -5.21932,-1.62524 -10.43287,-3.26807 -15.64258,-4.92383 -1.01372,-0.32142 -1.01372,-0.32142 -2.04791,-0.64935 -1.70079,-0.5419 -3.39885,-1.09234 -5.09662,-1.64361 -0.88664,-0.28771 -1.77327,-0.57541 -2.68677,-0.87183 -2.1687,-0.83521 -2.1687,-0.83521 -5.1687,-2.83521 -4.26651,-0.38661 -8.54211,-0.32916 -12.82398,-0.31567 -2.10282,0.003 -4.20423,-0.0203 -6.30688,-0.0457 -1.34049,-0.002 -2.68099,-0.003 -4.02149,-0.002 -1.82519,-0.004 -1.82519,-0.004 -3.68725,-0.009 -3.56773,0.4203 -4.39448,1.22301 -7.1604,3.37231 -2.98157,1.18491 -6.04867,2.07743 -9.1211,2.9961 -0.89574,0.27482 -1.79148,0.54963 -2.71437,0.83278 -2.86509,0.87713 -5.73343,1.74305 -8.60203,2.60862 -1.89541,0.57804 -3.7906,1.15681 -5.68555,1.73633 -3.64787,1.11522 -7.29838,2.22117 -10.95068,3.32178 -3.01197,0.91131 -6.01048,1.8496 -8.99707,2.84082 -1.14324,0.37496 -2.28648,0.74991 -3.46436,1.13623 -0.95399,0.32364 -1.90797,0.64727 -2.89087,0.98071 -2.99203,0.63542 -4.71313,0.64942 -7.57397,-0.45337 -1.38697,-2.02075 -1.38697,-2.02075 -2.47266,-4.73828 -0.41854,-1.02907 -0.83708,-2.05815 -1.26831,-3.1184 -0.43611,-1.14042 -0.87221,-2.28084 -1.32153,-3.45582 -0.46898,-1.19262 -0.93796,-2.38524 -1.42115,-3.61401 -8.70125,-22.36667 -16.18549,-45.23058 -21.93115,-68.53369 -2.03413,-10.5326 -2.03413,-10.5326 -8.26758,-18.66968 -6.69644,-2.74275 -14.2617,-2.30183 -21.38613,-2.35119 -22.502322,-0.18974 -46.337252,-0.43669 -63.931492,-16.51893 -13.83191,-15.39279 -17.60146,-34.36322 -19,-54.3125 -1.22377,-17.31546 -5.66451,-30.87225 -19,-42.6875 -1.0789999,-0.83894 -2.1618899,-1.67292 -3.2499999,-2.5 -4.15117,-3.77378 -6.29127995,-7.45743 -7.13280995,-13.03906 -0.75997,-17.5805 10.99539985,-33.43582 20.07542985,-47.59253 9.56114,-14.99879 12.65147,-25.61969 9.80738,-43.24341 -5.4019,-36.3138 6.589,-77.94207 27.51562,-107.953124 9.29206,-12.511354 22.11961,-24.30302 38.10938,-26.671875 7.020142,-0.401789 13.117592,2.099822 18.875002,6 0.41572,-0.877852 0.41572,-0.877852 0.83984,-1.773438 3.97707,-7.632757 10.32837,-13.996733 16.16016,-20.226562 1.21816,-1.316777 1.21816,-1.316777 2.46094,-2.660157 11.76049,-12.272588 25.4555,-21.61809 40.53906,-29.339843 0.92546,-0.477114 0.92546,-0.477114 1.86963,-0.963867 56.77867,-28.87966 131.24829,-21.222612 179.66943,20.979552 z"
|
||||
fill="#4cb96f"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 104.48036,74.209386 c 14.01919,9.544982 17.28909,27.479924 20.9375,43.000004 0.34781,1.46349 0.34781,1.46349 0.70264,2.95654 3.9666,16.83356 7.13574,33.77169 10.17238,50.78966 4.94765,27.65632 10.55273,55.18357 18.12498,82.2538 0.21592,0.77408 0.43184,1.54816 0.6543,2.3457 5.69367,20.18367 12.22766,40.2264 22.3457,58.6543 0.69292,1.29807 1.38562,2.59625 2.07813,3.89453 4.85041,8.96022 10.35159,17.39712 16.05078,25.83594 2.51273,3.72383 4.97364,7.47022 7.37109,11.26953 0.58781,0.92813 1.17563,1.85625 1.78125,2.8125 4.16437,7.72302 6.41065,15.04331 7.34375,23.75 0.11916,1.03649 0.23832,2.07297 0.36108,3.14087 1.31022,12.42718 1.79484,24.92019 2.28003,37.40234 0.32998,8.23421 0.78685,16.41561 1.69092,24.60913 0.0781,0.7089 0.15615,1.4178 0.23659,2.14818 1.638,13.89438 5.50414,26.91584 10.16429,40.07887 0.3408,0.97437 0.68159,1.94874 1.03272,2.95264 0.30969,0.86472 0.61939,1.72944 0.93847,2.62036 0.70466,2.61015 0.719,4.79568 0.6709,7.48511 -6.7243,2.12356 -13.45057,4.24083 -20.17822,6.35376 -2.28757,0.71926 -4.57468,1.43995 -6.86133,2.16211 -3.29033,1.03886 -6.58224,2.07253 -9.87451,3.10522 -1.52846,0.48442 -1.52846,0.48442 -3.0878,0.97863 -4.64994,1.45342 -9.14539,2.79279 -13.99814,3.40028 -4.25493,-8.82759 -7.84647,-17.67925 -11.0625,-26.9375 -0.40799,-1.15951 -0.81598,-2.31902 -1.23633,-3.51367 -0.82345,-2.34028 -1.64329,-4.68184 -2.45947,-7.02466 -0.83606,-2.3729 -1.68896,-4.73992 -2.55762,-7.10107 -2.70984,-7.38288 -4.90529,-14.70373 -6.68408,-22.3606 -1.68682,-6.94843 -3.38042,-13.87552 -5.5625,-20.6875 -0.35191,-1.10602 -0.70383,-2.21203 -1.06641,-3.35156 -2.24045,-4.9405 -4.92423,-8.26055 -9.80859,-10.83594 -6.07494,-2.02498 -11.81683,-2.33875 -18.15234,-2.39844 -1.68947,-0.0281 -3.37892,-0.0568 -5.06836,-0.0859 -2.62573,-0.0409 -5.25129,-0.0778 -7.8772,-0.10449 C 84.939569,413.59976 66.85999,412.17514 52.382702,398.4047 38.718144,383.13734 36.06592,361.22023 35.364635,341.59611 34.551434,325.69568 26.593093,312.67134 15.042858,302.14689 c -1.235459,-1.09677 -2.472338,-2.19194 -3.710937,-3.28516 -3.7754319,-3.43635 -3.7754319,-3.43635 -4.1874999,-7.05078 -0.586941,-17.20186 10.8112229,-31.757 19.6821279,-45.75171 6.320153,-10.07205 10.787181,-18.70157 10.778809,-30.72485 6.45e-4,-0.94948 0.0013,-1.89895 0.002,-2.8772 -0.04662,-6.22488 -0.64097,-12.10354 -1.732422,-18.23608 -4.852679,-31.97792 8.967014,-72.55721 27.833252,-98.270269 9.00489,-11.875077 24.526541,-27.04615 40.772172,-21.741455 z"
|
||||
fill="#eff7f9"
|
||||
id="path2" />
|
||||
<path
|
||||
d="m 406.41786,73.209386 c 19.25538,6.768868 32.14526,23.622413 40.86719,41.308594 12.36273,26.41954 19.99939,56.17564 15.63281,85.25391 -3.53699,23.64241 4.2922,39.12412 17.125,58.5 5.48203,8.27792 12.41526,19.13006 12.625,29.25 0.0387,1.10795 0.0387,1.10795 0.0781,2.23828 -0.88208,6.58409 -4.79511,10.09514 -9.57813,14.13672 -13.9383,12.30953 -18.45791,26.64607 -19.75,44.6875 -1.35283,18.73921 -4.43964,38.99647 -18.8125,52.5 -12.2884,9.85902 -27.59803,12.18836 -42.92969,12.33594 -1.66016,0.0281 -3.32032,0.0567 -4.98047,0.0859 -2.57568,0.0412 -5.15117,0.0781 -7.72705,0.1045 -15.51745,-0.24013 -15.51745,-0.24013 -29.42529,5.72363 -4.7446,6.41917 -6.8127,13.74762 -8.5625,21.4375 -1.9491,8.124 -4.15709,16.07942 -6.77002,24.01611 -0.93383,2.85328 -1.8442,5.71376 -2.75732,8.57373 -4.93702,15.29249 -10.60339,30.12249 -17.03516,44.84769 -6.07975,-0.65079 -11.63799,-2.2844 -17.45703,-4.125 -1.47626,-0.46044 -1.47626,-0.46044 -2.98235,-0.93018 -2.06909,-0.64641 -4.13738,-1.29538 -6.20491,-1.94677 -3.17349,-0.99971 -6.34931,-1.99174 -9.52563,-2.98243 -2.01251,-0.62976 -4.02489,-1.25995 -6.03711,-1.89062 -0.95204,-0.29793 -1.90408,-0.59587 -2.88497,-0.90283 -1.31728,-0.41548 -1.31728,-0.41548 -2.66117,-0.83936 -1.1608,-0.36521 -1.1608,-0.36521 -2.34505,-0.73779 -1.90178,-0.64502 -1.90178,-0.64502 -3.90178,-1.64502 0.58313,-7.25383 2.64659,-13.78262 4.875,-20.6875 4.45365,-13.85851 7.51171,-27.09272 8.6875,-41.625 0.12559,-1.45997 0.25124,-2.91993 0.37695,-4.37988 0.69327,-8.30279 1.20649,-16.60591 1.58789,-24.92871 0.0411,-0.88719 0.0821,-1.77437 0.12441,-2.68843 0.19608,-4.31989 0.37451,-8.63956 0.52623,-12.96123 0.77791,-20.25966 6.05514,-34.69661 17.57202,-51.41675 15.399,-22.76918 26.3875,-45.27462 35.25,-71.3125 0.28617,-0.83789 0.57234,-1.67578 0.86719,-2.53906 11.82929,-35.01939 18.09788,-71.56471 24.78988,-107.8216 13.6546,-73.951398 13.6546,-73.951398 29.21402,-87.237001 3.86755,-2.547613 7.67898,-2.136857 12.12891,-1.402343 z"
|
||||
fill="#eff7f9"
|
||||
id="path3" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 472 KiB |
|
|
@ -2,16 +2,43 @@
|
|||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="128.27412mm"
|
||||
width="128.27411mm"
|
||||
height="145.65482mm"
|
||||
viewBox="0 0 128.27413 145.65482"
|
||||
viewBox="0 0 128.27411 145.65482"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
xml:space="preserve"
|
||||
inkscape:export-filename="../../../../../Downloads/xrforge.svg"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
sodipodi:docname="logo.svg"
|
||||
inkscape:dataloss="true"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.56323837"
|
||||
inkscape:cx="226.36952"
|
||||
inkscape:cy="409.24058"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1030"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" /><defs
|
||||
id="defs2"><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient12286"><stop
|
||||
style="stop-color:#ea0bfe;stop-opacity:0.11569338;"
|
||||
offset="0"
|
||||
|
|
@ -19,6 +46,7 @@
|
|||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop12284" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient12159"><stop
|
||||
style="stop-color:#fe83ff;stop-opacity:0.35045233;"
|
||||
offset="0"
|
||||
|
|
@ -26,6 +54,7 @@
|
|||
style="stop-color:#3c9cff;stop-opacity:0.32712477;"
|
||||
offset="1"
|
||||
id="stop12157" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient12153"><stop
|
||||
style="stop-color:#fe83ff;stop-opacity:0.29342434;"
|
||||
offset="0"
|
||||
|
|
@ -33,6 +62,7 @@
|
|||
style="stop-color:#3c9cff;stop-opacity:0.31577286;"
|
||||
offset="1"
|
||||
id="stop12151" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient12139"><stop
|
||||
style="stop-color:#ea0bfe;stop-opacity:0.50826901;"
|
||||
offset="0"
|
||||
|
|
@ -40,6 +70,7 @@
|
|||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop12137" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient12102"><stop
|
||||
style="stop-color:#fe83ff;stop-opacity:1;"
|
||||
offset="0"
|
||||
|
|
@ -47,6 +78,7 @@
|
|||
style="stop-color:#3c9cff;stop-opacity:0.81848603;"
|
||||
offset="1"
|
||||
id="stop12100" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient7688"><stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
|
|
@ -68,6 +100,7 @@
|
|||
x2="273.12695"
|
||||
y2="24.048252"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient7688"
|
||||
id="linearGradient7692"
|
||||
x1="115.42191"
|
||||
|
|
@ -75,6 +108,7 @@
|
|||
x2="117.16759"
|
||||
y2="131.87457"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient12102"
|
||||
id="linearGradient12104"
|
||||
x1="54.029213"
|
||||
|
|
@ -82,6 +116,7 @@
|
|||
x2="176.85757"
|
||||
y2="71.733955"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient12159"
|
||||
id="linearGradient12108"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
|
|
@ -90,6 +125,7 @@
|
|||
x2="176.85757"
|
||||
y2="71.733955"
|
||||
gradientTransform="translate(0,4.7625002)" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient12153"
|
||||
id="linearGradient12112"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
|
|
@ -98,6 +134,7 @@
|
|||
y1="71.733955"
|
||||
x2="176.85757"
|
||||
y2="71.733955" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient12286"
|
||||
id="linearGradient12141"
|
||||
x1="137.33427"
|
||||
|
|
@ -106,6 +143,7 @@
|
|||
y2="88.766113"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.2850723,0,0,1.2367478,-50.791853,-16.999519)" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient12139"
|
||||
id="linearGradient12239"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
|
|
@ -115,8 +153,9 @@
|
|||
y2="88.766113"
|
||||
gradientTransform="matrix(-1.2669282,0,0,1.2603766,278.3952,-19.18513)" /><filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter12743"
|
||||
x="-0.079006466"
|
||||
x="-0.079006463"
|
||||
y="-0.2479955"
|
||||
width="1.1580434"
|
||||
height="1.4959902"><feFlood
|
||||
|
|
@ -143,11 +182,12 @@
|
|||
result="composite2"
|
||||
id="feComposite12741" /></filter><filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter13745"
|
||||
x="-0.13442897"
|
||||
y="-0.73597233"
|
||||
width="1.2688579"
|
||||
height="2.4719447"><feFlood
|
||||
x="-0.13448349"
|
||||
y="-0.73597109"
|
||||
width="1.268967"
|
||||
height="2.4719422"><feFlood
|
||||
flood-opacity="1"
|
||||
flood-color="rgb(26,135,255)"
|
||||
result="flood"
|
||||
|
|
@ -170,6 +210,7 @@
|
|||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite13743" /></filter><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient12286"
|
||||
id="linearGradient14475"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
|
|
@ -178,22 +219,41 @@
|
|||
y1="88.766113"
|
||||
x2="177.37935"
|
||||
y2="88.766113" /></defs><g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-51.358538,-4.8451999)"><path
|
||||
sodipodi:type="star"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:2.3;stroke-dasharray:none"
|
||||
id="path1638-3"
|
||||
inkscape:flatsided="true"
|
||||
sodipodi:sides="6"
|
||||
sodipodi:cx="138.75616"
|
||||
sodipodi:cy="263.41873"
|
||||
sodipodi:r1="70.000412"
|
||||
sodipodi:r2="60.622131"
|
||||
sodipodi:arg1="-2.6179939"
|
||||
sodipodi:arg2="-2.0943951"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 78.134029,228.41853 60.622131,-35.00021 60.62214,35.0002 0,70.00042 -60.62213,35.0002 -60.62214,-35.0002 z"
|
||||
inkscape:transform-center-x="-4.0923148e-06"
|
||||
inkscape:transform-center-y="-2.6621219e-06"
|
||||
transform="matrix(1.0382846,0,0,1.0210168,-28.572793,-191.28234)" /><g
|
||||
id="g12126"><path
|
||||
id="g12126"
|
||||
inkscape:label="lines"><path
|
||||
style="fill:none;fill-opacity:1;stroke:url(#linearGradient12104);stroke-width:0.5;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 54.029427,71.745371 176.85736,71.722537"
|
||||
id="path12042" /><path
|
||||
id="path12042"
|
||||
inkscape:label="line" /><path
|
||||
style="fill:none;fill-opacity:1;stroke:url(#linearGradient12108);stroke-width:0.6;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 54.029427,76.507874 176.85736,76.48504"
|
||||
id="path12106" /><path
|
||||
id="path12106"
|
||||
inkscape:label="line" /><path
|
||||
style="fill:none;fill-opacity:1;stroke:url(#linearGradient12112);stroke-width:0.8;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 54.029427,91.32455 176.85736,91.301716"
|
||||
id="path12110" /><path
|
||||
id="path12110"
|
||||
inkscape:label="line" /><path
|
||||
style="fill:url(#linearGradient12239);fill-opacity:1;stroke:none;stroke-width:2.416;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 104.40252,71.315371 53.668551,108.90527 v 5.16648 z"
|
||||
id="path12133-5" /><path
|
||||
|
|
@ -202,10 +262,12 @@
|
|||
id="path12133" /><path
|
||||
style="fill:url(#linearGradient12141);fill-opacity:1;stroke:none;stroke-width:2.41042;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 115.37951,70.94784 5.14781,74.3852 c 0,0 -4.42958,5.94658 -10.22727,-0.9757 z"
|
||||
id="path12280" /></g><path
|
||||
id="path12280"
|
||||
sodipodi:nodetypes="cccc" /></g><path
|
||||
style="fill:url(#linearGradient7692);fill-opacity:1;stroke:none;stroke-width:2.3;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 53.906377,71.659657 H 177.09207 l 0.24519,40.701533 -61.93861,35.18517 -61.54407,-35.3442 z"
|
||||
id="path7625" /><g
|
||||
id="path7625"
|
||||
sodipodi:nodetypes="cccccc" /><g
|
||||
id="g4494"
|
||||
transform="matrix(2.7825702,0,0,3.2095953,58.857189,44.497537)"
|
||||
style="filter:url(#filter12743)"><g
|
||||
|
|
@ -238,6 +300,7 @@
|
|||
x="69.809654"
|
||||
y="107.18471"
|
||||
id="text1004"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1002"
|
||||
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:19.7556px;font-family:Montserrat;-inkscape-font-specification:'Montserrat Thin';stroke-width:0.5;stroke-dasharray:none"
|
||||
x="69.809654"
|
||||
|
|
@ -247,16 +310,18 @@
|
|||
x="160.00148"
|
||||
y="107.33738"
|
||||
id="text1004-3"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1002-6"
|
||||
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:19.7556px;font-family:Montserrat;-inkscape-font-specification:'Montserrat Thin';stroke-width:0.5;stroke-dasharray:none"
|
||||
x="160.00148"
|
||||
y="107.33738">]</tspan></text><text
|
||||
xml:space="preserve"
|
||||
style="font-weight:100;font-size:16.2278px;font-family:Montserrat;-inkscape-font-specification:'Montserrat Thin';text-align:center;letter-spacing:0.529167px;writing-mode:tb-rl;text-orientation:upright;text-anchor:middle;fill:#020202;fill-opacity:1;stroke:#000000;stroke-width:2.3;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter13745)"
|
||||
x="75.235199"
|
||||
y="106.55447"
|
||||
x="85.100029"
|
||||
y="91.992111"
|
||||
id="text3268"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3266"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:16.2278px;font-family:Montserrat;-inkscape-font-specification:'Montserrat Bold';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.3;stroke-opacity:1"
|
||||
x="75.235199"
|
||||
y="106.55447">XR Forge</tspan></text></g></svg>
|
||||
x="85.100029"
|
||||
y="91.992111">XR Forge</tspan></text></g></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 16 KiB |
|
|
@ -1,34 +0,0 @@
|
|||
#sidebar td > label > i {
|
||||
opacity:0.4;
|
||||
}
|
||||
|
||||
#toggle:checked + .detail-tooltip {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hidden-tooltip {
|
||||
display: none;
|
||||
margin-top:7px;
|
||||
max-height: 230px;
|
||||
overflow-y:scroll;
|
||||
background: var(--bs-body-bg);
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
/* Show the tooltip when the corresponding checkbox is checked */
|
||||
input[type="checkbox"]:checked + .hidden-tooltip {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#model_notes {
|
||||
min-height: 50vh;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
color: #CCF;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.col-form-label{
|
||||
width:111px;
|
||||
}
|
||||
126
nix/docker.nix
|
|
@ -22,98 +22,14 @@ let
|
|||
# generate the reproducable blob below via:
|
||||
# $ nix-shell -p nix-prefetch-docker --run 'nix-prefetch-docker ghcr.io/manyfold3d/manyfold-solo 0.120.0'
|
||||
|
||||
#manyfoldImage = pkgs.dockerTools.pullImage {
|
||||
# imageName = "ghcr.io/manyfold3d/manyfold-solo";
|
||||
# imageDigest = "sha256:6250e562a05bf9476ddcfdc897a7b03cbf2090c727d9fe051afde486579b54a6";
|
||||
# sha256 = "sha256-V5y1N0l4JQjVDQbboGYX15MQaImXtP/HpNwPjDtxeJQ=";
|
||||
# finalImageName = "ghcr.io/manyfold3d/manyfold-solo";
|
||||
# finalImageTag = "0.121.0";
|
||||
#};
|
||||
|
||||
manyfoldImage = pkgs.dockerTools.pullImage {
|
||||
imageName = "ghcr.io/manyfold3d/manyfold-solo";
|
||||
imageDigest = "sha256:465399a2d296034ef84dba18a13744b567694c652387bd17fe97d51c672d1fa9";
|
||||
hash = "sha256-j7YSUGRFUDh6FJ1CrIQEzGU/0B8uPO8y1kd9lYLad4w=";
|
||||
finalImageName = "ghcr.io/manyfold3d/manyfold-solo";
|
||||
finalImageTag = "latest";
|
||||
imageName = "ghcr.io/manyfold3d/manyfold-solo";
|
||||
imageDigest = "sha256:6250e562a05bf9476ddcfdc897a7b03cbf2090c727d9fe051afde486579b54a6";
|
||||
sha256 = "sha256-V5y1N0l4JQjVDQbboGYX15MQaImXtP/HpNwPjDtxeJQ=";
|
||||
finalImageName = "ghcr.io/manyfold3d/manyfold-solo";
|
||||
finalImageTag = "0.121.0";
|
||||
};
|
||||
|
||||
#### JANUSXR Stack
|
||||
|
||||
janusweb = builtins.fetchTarball {
|
||||
url = "https://github.com/coderofsalvation/janusweb/releases/download/1.5.56-xrf/janusweb-1.5.56.tar.gz";
|
||||
# Get the SHA256 hash by running: nix-prefetch-url --unpack <URL>
|
||||
sha256 = "0zkmfv07zxdf1nkhgr4g959fj86p2yp9f7n1ll1zyhlm186sfh31";
|
||||
};
|
||||
|
||||
# Fetch the source from the GitHub tag
|
||||
corsanywhere = pkgs.fetchzip {
|
||||
url = "https://github.com/Rob--W/cors-anywhere/archive/0.4.4.tar.gz";
|
||||
# You need to calculate the correct SHA256 hash for version 0.4.4
|
||||
# You can get this by running 'nix-prefetch-url --unpack https://github.com/Rob--W/cors-anywhere/archive/0.4.4.tar.gz'
|
||||
sha256 = "0zb3xzlpbc400rvibm66qb27y0lla8sml1ns41ksxcc0l0kp0bwz";
|
||||
};
|
||||
|
||||
janusServer = pkgs.fetchurl {
|
||||
url = "https://github.com/janusvr/janus-server/releases/download/v0.2.1/janus_server-linux";
|
||||
# You can calculate the hash using: nix-prefetch-url https://...
|
||||
sha256 = "1rrmabvqn4lm7c2k1q2crqwyk7lvpmfihc3hx3qx2hzsjyp5v3ja";
|
||||
name = "janus_server-linux";
|
||||
};
|
||||
|
||||
## 2. Create a minimal derivation to make the file executable
|
||||
#janusServerBin = pkgs.stdenv.mkDerivation {
|
||||
# pname = "janus-server";
|
||||
# version = "1.0";
|
||||
# src = janusServer;
|
||||
# dontUnpack = true;
|
||||
# dontBuild = true;
|
||||
# installPhase = ''
|
||||
# mkdir -p $out/bin
|
||||
# cp $src $out/bin/janus_server-linux
|
||||
# chmod +x $out/bin/janus_server-linux
|
||||
# '';
|
||||
#};
|
||||
|
||||
janus = pkgs.runCommand "janusweb-content" {} ''
|
||||
mkdir -p $out/mnt/janusweb $out/root/corsanywhere $out/root
|
||||
cp -rT ${janusweb} $out/mnt/janusweb
|
||||
cp -rT ${corsanywhere} $out/root/corsanywhere/.
|
||||
cp -rT ${janusServer} $out/root/janus_server-linux
|
||||
'';
|
||||
|
||||
#### XR FRAGMENTS
|
||||
|
||||
## $ nix-shell -p nix-prefetch-git --command 'nix-prefetch-git https://codeberg.org/coderofsalvation/xrfragment.git <commit>'
|
||||
xrfragmentsRepo = pkgs.fetchFromGitea {
|
||||
"domain" = "codeberg.org";
|
||||
"owner" = "coderofsalvation";
|
||||
"repo" = "xrfragment";
|
||||
"rev" = "823736a74dbdabd46924ebbc3dc58a076c3c900b";
|
||||
"hash" = "sha256-P/RVwoDu0EYfwc0n2h1f4j0JZshtE4AtQsTU9iEkAcA=";
|
||||
};
|
||||
|
||||
xrfragments = pkgs.runCommand "xrfragments-content" {} ''
|
||||
mkdir -p $out/mnt/asset/xrfragments $out/mnt/templates/xrfragments
|
||||
cp -r ${xrfragmentsRepo}/assets/library $out/mnt/asset/xrfragments/\#1
|
||||
find ${xrfragmentsRepo}//assets/template -maxdepth 1 -mindepth 1 -type d | awk '{ system("cp -r "$0" '$out'/mnt/templates/xrfragments/#"(NR+1)) }'
|
||||
'';
|
||||
|
||||
|
||||
## generate the reproducable blob below via:
|
||||
## $ nix-shell -p nix-prefetch-github --command 'nix-prefetch-github assimp assimp --rev e778c84cd62bc8b38d8e491ad3d2c27cb8ed37d5'
|
||||
#assimpSrc = pkgs.fetchFromGitHub {
|
||||
# "owner" = "assimp";
|
||||
# "repo" = "assimp";
|
||||
# "rev" = "e778c84cd62bc8b38d8e491ad3d2c27cb8ed37d5";
|
||||
# "hash" = "sha256-ja5pFwpnzLT2MDIR8ISwC6+eA5UXyqRZW2CMCCrF1Q0=";
|
||||
#};
|
||||
#myAssimp = pkgs.pkgsStatic.assimp.overrideAttrs (oldAttrs: {
|
||||
# inherit assimpSrc; # Set the source to the fetched commit
|
||||
# src = assimpSrc;
|
||||
#});
|
||||
|
||||
|
||||
in
|
||||
rec
|
||||
{
|
||||
|
|
@ -126,26 +42,19 @@ rec
|
|||
fromImage = manyfoldImage;
|
||||
|
||||
# add nix pkgs + local files
|
||||
copyToRoot = pkgs.buildEnv {
|
||||
contents = pkgs.buildEnv {
|
||||
name = "image-root";
|
||||
pathsToLink = ["/manyfold" "/bin" "/mnt" "/root"];
|
||||
pathsToLink = ["/manyfold" "/bin"];
|
||||
paths = [
|
||||
#pkgs.pkgsStatic.rsync
|
||||
pkgs.rsync
|
||||
pkgs.sqlite
|
||||
pkgs.rclone
|
||||
pkgs.fuse3
|
||||
pkgs.janus-gateway # webrtc voip for JanusXR
|
||||
pkgs.nodejs_20 # for corsanywhere
|
||||
#pkgs.acl # getfacl e.g.
|
||||
#pkgs.inotify-tools # inotifywait e.g.
|
||||
pkgs.zip # inotifywait e.g.
|
||||
pkgs.assimp
|
||||
##pkgs.ts # job management
|
||||
#myAssimp # updated build of assimp
|
||||
janus
|
||||
xrfragments
|
||||
../.
|
||||
pkgs.pkgsStatic.rsync
|
||||
pkgs.pkgsStatic.sqlite
|
||||
pkgs.pkgsStatic.rclone
|
||||
pkgs.pkgsStatic.fuse3
|
||||
pkgs.pkgsStatic.acl # getfacl e.g.
|
||||
pkgs.pkgsStatic.inotify-tools # inotifywait e.g.
|
||||
pkgs.pkgsStatic.zip # inotifywait e.g.
|
||||
pkgs.pkgsStatic.ts # job management
|
||||
./..
|
||||
];
|
||||
};
|
||||
|
||||
|
|
@ -153,9 +62,6 @@ rec
|
|||
Cmd = ["/init"];
|
||||
# We substitute the /init entrypoing with our own wrapper
|
||||
EntryPoint = ["/manyfold/cli/manyfold.sh" "boot"];
|
||||
Env = [
|
||||
"LD_PRELOAD=" # unset LD_PRELOAD (some package is setting it a nonexisting lib)
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -55,11 +55,6 @@
|
|||
echo " $ # copy image to other server
|
||||
echo " $ docker save xrforge | bzip2 | ssh user@host docker load"
|
||||
echo ""
|
||||
echo "Development:"
|
||||
echo ""
|
||||
echo "" $ cd xrforge-webxr && bun run build && cp dist/xrforge.html ../manyfold/usr/src/app/public/view/index.html
|
||||
echo "" $ manyfold/cli/manyfold.sh run -e DEV=1 -v ./manyfold/usr/src/app/public/view:/usr/src/app/public/view
|
||||
echo ""
|
||||
|
||||
'';
|
||||
};
|
||||
|
|
|
|||
BIN
xrforge.jpg
|
Before Width: | Height: | Size: 85 KiB |