Skip to content

Commit edb7e07

Browse files
committed
feature: added excel support to playground
1 parent 07e07e1 commit edb7e07

File tree

8 files changed

+157
-12
lines changed

8 files changed

+157
-12
lines changed

shell.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ pkgs.mkShell {
5656
# WASM build tools
5757
pkgs.emscripten
5858
pkgs.autoconf
59+
pkgs.cmake
5960
pkgs.wget
6061
pkgs.gnutar
6162
pkgs.xz

wasm/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ manifest.json
66
/libxml2-*
77
/libpg_query
88
/php-ext-snappy
9-
9+
/libzip-*
1010
# Build logs
1111
*.log
1212

wasm/build.sh

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,68 @@ if [ ! -d "$LIBPG_QUERY_DIR" ]; then
6666
cd $PROJECT_ROOT
6767
fi
6868

69+
echo "Build libzip for WebAssembly"
70+
LIBZIP_VERSION=1.11.3
71+
LIBZIP_DIR=libzip-$LIBZIP_VERSION
72+
LIBZIP_INSTALL_DIR="$PROJECT_ROOT/$LIBZIP_DIR/install"
73+
74+
# Check for installed library, not just source directory
75+
if [ ! -f "$LIBZIP_INSTALL_DIR/lib/libzip.a" ]; then
76+
# First, ensure Emscripten's zlib port is built by triggering a compile
77+
# This downloads and builds zlib to the Emscripten cache
78+
echo "int main(){return 0;}" > /tmp/zlib_test.c
79+
emcc -sUSE_ZLIB=1 /tmp/zlib_test.c -o /tmp/zlib_test.js 2>/dev/null || true
80+
rm -f /tmp/zlib_test.c /tmp/zlib_test.js /tmp/zlib_test.wasm
81+
82+
# Get Emscripten cache path and locate zlib
83+
EM_CACHE=$(em-config CACHE)
84+
ZLIB_LIBRARY="$EM_CACHE/sysroot/lib/wasm32-emscripten/lto/libz.a"
85+
ZLIB_INCLUDE_DIR="$EM_CACHE/sysroot/include"
86+
87+
echo "Using zlib from Emscripten cache:"
88+
echo " ZLIB_LIBRARY=$ZLIB_LIBRARY"
89+
echo " ZLIB_INCLUDE_DIR=$ZLIB_INCLUDE_DIR"
90+
91+
if [ ! -f "$ZLIB_LIBRARY" ]; then
92+
echo "ERROR: zlib library not found at $ZLIB_LIBRARY"
93+
exit 1
94+
fi
95+
96+
if [ ! -e $LIBZIP_DIR.tar.xz ]; then
97+
wget https://libzip.org/download/libzip-$LIBZIP_VERSION.tar.xz
98+
fi
99+
tar xf $LIBZIP_DIR.tar.xz
100+
cd $LIBZIP_DIR
101+
102+
mkdir -p build && cd build
103+
104+
# Configure libzip for WebAssembly using CMake
105+
# Provide explicit paths to Emscripten's zlib (from its ports system)
106+
# Disable encryption and optional compression to minimize dependencies
107+
emcmake cmake .. \
108+
-DCMAKE_INSTALL_PREFIX=$LIBZIP_INSTALL_DIR \
109+
-DZLIB_LIBRARY=$ZLIB_LIBRARY \
110+
-DZLIB_INCLUDE_DIR=$ZLIB_INCLUDE_DIR \
111+
-DBUILD_SHARED_LIBS=OFF \
112+
-DENABLE_COMMONCRYPTO=OFF \
113+
-DENABLE_GNUTLS=OFF \
114+
-DENABLE_MBEDTLS=OFF \
115+
-DENABLE_OPENSSL=OFF \
116+
-DENABLE_WINDOWS_CRYPTO=OFF \
117+
-DENABLE_BZIP2=OFF \
118+
-DENABLE_LZMA=OFF \
119+
-DENABLE_ZSTD=OFF \
120+
-DBUILD_TOOLS=OFF \
121+
-DBUILD_REGRESS=OFF \
122+
-DBUILD_EXAMPLES=OFF \
123+
-DBUILD_DOC=OFF
124+
125+
emmake make -j$(nproc)
126+
emmake make install
127+
128+
cd $PROJECT_ROOT
129+
fi
130+
69131
echo "Download and extract PHP if needed"
70132
if [ ! -d "$PHP_PATH" ]; then
71133
if [ ! -e $PHP_PATH.tar.xz ]; then
@@ -94,9 +156,15 @@ cp -r "$SNAPPY_EXT_DIR" "$SNAPPY_EXT_DST"
94156
echo "Configure PHP"
95157

96158
# Use -Oz for size optimization instead of -O3 for speed
97-
export CFLAGS="-Oz -flto -fPIC -g0 -DZEND_MM_ERROR=0 -I$LIBXML2_INSTALL_DIR/include/libxml2 -I$LIBPG_QUERY_INSTALL_DIR -I$LIBPG_QUERY_INSTALL_DIR/src -sUSE_ZLIB=1"
159+
export CFLAGS="-Oz -flto -fPIC -g0 -DZEND_MM_ERROR=0 -I$LIBXML2_INSTALL_DIR/include/libxml2 -I$LIBPG_QUERY_INSTALL_DIR -I$LIBPG_QUERY_INSTALL_DIR/src -I$LIBZIP_INSTALL_DIR/include -sUSE_ZLIB=1"
98160
export CXXFLAGS="-Oz -flto -fPIC -g0 -std=c++11 -sUSE_ZLIB=1"
99-
export LDFLAGS="-L$LIBXML2_INSTALL_DIR/lib -L$LIBPG_QUERY_INSTALL_DIR -sUSE_ZLIB=1"
161+
export LDFLAGS="-L$LIBXML2_INSTALL_DIR/lib -L$LIBPG_QUERY_INSTALL_DIR -L$LIBZIP_INSTALL_DIR/lib -sUSE_ZLIB=1"
162+
163+
# Set PKG_CONFIG_PATH so PHP configure can find libzip
164+
# Note: emconfigure overrides PKG_CONFIG_PATH with PKG_CONFIG_LIBDIR, so we also set LIBZIP_* directly
165+
export PKG_CONFIG_PATH="$LIBZIP_INSTALL_DIR/lib/pkgconfig:${PKG_CONFIG_PATH:-}"
166+
export LIBZIP_CFLAGS="-I$LIBZIP_INSTALL_DIR/include"
167+
export LIBZIP_LIBS="-L$LIBZIP_INSTALL_DIR/lib -lzip"
100168

101169
cd $PHP_PATH
102170

@@ -106,6 +174,7 @@ cd $PHP_PATH
106174
# - xml, dom, xmlreader, xmlwriter: required by flow-php/etl-adapter-xml
107175
# - phar, mbstring: essential PHP extensions
108176
# - iconv: required by symfony/polyfill-mbstring
177+
# - zip: required by flow-php/etl-adapter-excel (XLSX files are ZIP archives)
109178

110179
# Fix permissions for build scripts
111180
chmod +x buildconf build/config-stubs build/shtool 2>/dev/null || true
@@ -143,7 +212,8 @@ emconfigure ./configure \
143212
--enable-xmlwriter \
144213
--enable-pg-query \
145214
--with-pg-query=$LIBPG_QUERY_INSTALL_DIR \
146-
--enable-snappy
215+
--enable-snappy \
216+
--with-zip
147217

148218
if [ $? -ne 0 ]; then
149219
echo "emconfigure failed. Content of config.log:"
@@ -189,7 +259,7 @@ emcc $CFLAGS $LDFLAGS \
189259
-s ASYNCIFY=1 \
190260
-s STACK_OVERFLOW_CHECK=0 \
191261
-s SAFE_HEAP=0 \
192-
libs/libphp.a pib_eval.o $LIBXML2_INSTALL_DIR/lib/libxml2.a $LIBPG_QUERY_INSTALL_DIR/libpg_query.a -o out/php.js
262+
libs/libphp.a pib_eval.o $LIBXML2_INSTALL_DIR/lib/libxml2.a $LIBPG_QUERY_INSTALL_DIR/libpg_query.a $LIBZIP_INSTALL_DIR/lib/libzip.a -o out/php.js
193263

194264
echo "Copy outputs to web/landing/assets/wasm"
195265
OUTPUT_DIR="$PROJECT_ROOT/../web/landing/assets/wasm"

web/landing/assets/controllers/playground_tabs_controller.js

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ export default class extends Controller {
7474

7575
try {
7676
const fullPath = `/workspace${filePath}`
77+
const fileName = filePath.split('/').pop()
78+
const extension = fileName.split('.').pop().toLowerCase()
79+
const isBinary = this.#isBinaryExtension(extension)
80+
81+
if (isBinary) {
82+
this.previewFileValue = filePath
83+
this.#setPreviewContent(`[Binary file: ${fileName}]\n\nClick the download button to save this file.`, filePath)
84+
this.activeTabValue = 'preview'
85+
this.#updateTabUI()
86+
this.#log('Binary file opened:', filePath)
87+
return
88+
}
89+
7790
const result = await this.wasmOutlet.readFile(fullPath)
7891

7992
if (!result.success || result.content === null || result.content === undefined) {
@@ -118,21 +131,39 @@ export default class extends Controller {
118131
this.openFile({ currentTarget: { dataset: { filePath } } })
119132
}
120133

121-
downloadPreviewFile(event) {
134+
async downloadPreviewFile(event) {
122135
if (event) {
123136
event.preventDefault()
124137
}
125138

126-
if (!this.previewFileValue || !this.#previewEditor) {
139+
if (!this.previewFileValue) {
127140
this.#log('No file to download')
128141
return
129142
}
130143

131144
try {
132-
const content = this.#previewEditor.state.doc.toString()
133145
const fileName = this.previewFileValue.split('/').pop()
146+
const extension = fileName.split('.').pop().toLowerCase()
147+
const isBinary = this.#isBinaryExtension(extension)
148+
149+
let blob
150+
if (isBinary && this.hasWasmOutlet) {
151+
const fullPath = `/workspace${this.previewFileValue}`
152+
const result = await this.wasmOutlet.readFile(fullPath, true)
153+
if (!result.success) {
154+
this.#log('Failed to read binary file:', this.previewFileValue)
155+
return
156+
}
157+
const mimeType = this.#getMimeType(extension)
158+
blob = new Blob([result.content], { type: mimeType })
159+
} else if (this.#previewEditor) {
160+
const content = this.#previewEditor.state.doc.toString()
161+
blob = new Blob([content], { type: 'text/plain' })
162+
} else {
163+
this.#log('No content to download')
164+
return
165+
}
134166

135-
const blob = new Blob([content], { type: 'text/plain' })
136167
const url = URL.createObjectURL(blob)
137168
const a = document.createElement('a')
138169
a.href = url
@@ -148,6 +179,32 @@ export default class extends Controller {
148179
}
149180
}
150181

182+
#isBinaryExtension(extension) {
183+
const binaryExtensions = ['xlsx', 'xls', 'zip', 'tar', 'gz', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'bmp', 'ico', 'parquet', 'avro', 'orc']
184+
return binaryExtensions.includes(extension)
185+
}
186+
187+
#getMimeType(extension) {
188+
const mimeTypes = {
189+
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
190+
'xls': 'application/vnd.ms-excel',
191+
'zip': 'application/zip',
192+
'tar': 'application/x-tar',
193+
'gz': 'application/gzip',
194+
'pdf': 'application/pdf',
195+
'png': 'image/png',
196+
'jpg': 'image/jpeg',
197+
'jpeg': 'image/jpeg',
198+
'gif': 'image/gif',
199+
'bmp': 'image/bmp',
200+
'ico': 'image/x-icon',
201+
'parquet': 'application/octet-stream',
202+
'avro': 'application/octet-stream',
203+
'orc': 'application/octet-stream'
204+
}
205+
return mimeTypes[extension] || 'application/octet-stream'
206+
}
207+
151208
#setPreviewContent(content, filePath) {
152209
if (!this.hasPreviewPanelTarget) {
153210
return

web/landing/assets/controllers/wasm_controller.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,8 @@ require '/workspace/bin/cs-fixer.php';
223223
})
224224
}
225225

226-
async readFile(path) {
227-
const content = this.#readFileSync(path)
226+
async readFile(path, binary = false) {
227+
const content = binary ? this.#readFileBinarySync(path) : this.#readFileSync(path)
228228
if (content === null) {
229229
return { success: false, error: 'File not found' }
230230
}
@@ -363,6 +363,23 @@ require '/workspace/bin/cs-fixer.php';
363363
}
364364
}
365365

366+
#readFileBinarySync(filePath) {
367+
if (!this.#phpModuleLoaded) {
368+
this.#logError('Cannot read file: PHP module not loaded yet')
369+
return null
370+
}
371+
372+
try {
373+
const FS = this.#phpModule.FS
374+
const content = FS.readFile(filePath)
375+
this.#log('File read successfully (binary):', filePath)
376+
return content
377+
} catch (error) {
378+
this.#logError('Error reading file:', error)
379+
return null
380+
}
381+
}
382+
366383
#uploadFile(filename, uint8Array) {
367384
if (!this.#phpModuleLoaded) {
368385
this.#logError('Cannot upload file: PHP module not loaded yet')

web/landing/assets/wasm/php.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/landing/assets/wasm/php.wasm

228 KB
Binary file not shown.
96.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)