1- <!DOCTYPE html>
1+ <!doctype html>
22< html lang ="en ">
3- < head >
4- < meta charset ="UTF-8 ">
5- < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
6- < script src ="flowquery.min.js "> </ script >
7- < title > FlowQuery</ title >
8- < style >
9- body {
10- display : flex;
11- flex-direction : column;
12- align-items : center;
13- justify-content : center;
14- height : 100vh ;
15- margin : 0 ; /* Prevent unexpected scroll issues */
16- padding : 0 ;
17- box-sizing : border-box;
18- }
19-
20- # input {
21- margin-top : 5px ;
22- width : 100% ;
23- height : 300px ;
24- font-family : monospace;
25- resize : none;
26- box-sizing : border-box; /* Include padding/border in width/height */
27- }
28-
29- # output {
30- margin-top : 5px ;
31- width : 100% ;
32- flex-grow : 1 ;
33- overflow-y : auto;
34- font-family : monospace;
35- box-sizing : border-box;
36- max-height : calc (100vh - 300px - 5px ); /* Prevent excessive growth */
37- }
38- </ style >
39- < script >
40- function createTable ( results ) {
41- const table = document . createElement ( "table" ) ;
42-
43- if ( results . length === 0 ) return table ;
44-
45- const headerRow = document . createElement ( "tr" ) ;
46- Object . entries ( results [ 0 ] ) . forEach ( ( [ key , _ ] ) => {
47- const th = document . createElement ( "th" ) ;
48- th . style . textAlign = "left" ;
49- th . textContent = key ;
50- headerRow . appendChild ( th ) ;
51- } ) ;
52-
53- table . appendChild ( headerRow ) ;
54-
55- results . forEach ( row => {
56- const tr = document . createElement ( "tr" ) ;
57- Object . entries ( row ) . forEach ( ( [ _ , value ] ) => {
58- const td = document . createElement ( "td" ) ;
59- if ( typeof value === "object" || Array . isArray ( value ) ) {
60- td . textContent = JSON . stringify ( value ) ;
61- } else {
62- td . textContent = value ;
63- }
64- tr . appendChild ( td ) ;
3+ < head >
4+ < meta charset ="UTF-8 " />
5+ < meta name ="viewport " content ="width=device-width, initial-scale=1.0 " />
6+ < script src ="flowquery.min.js "> </ script >
7+ < title > FlowQuery</ title >
8+ < style >
9+ body {
10+ display : flex;
11+ flex-direction : column;
12+ align-items : center;
13+ justify-content : center;
14+ height : 100vh ;
15+ margin : 0 ;
16+ padding : 0 ;
17+ box-sizing : border-box;
18+ }
19+
20+ # toolbar {
21+ width : 100% ;
22+ display : flex;
23+ gap : 5px ;
24+ padding : 5px 0 ;
25+ box-sizing : border-box;
26+ }
27+
28+ # toolbar button {
29+ font-family : monospace;
30+ cursor : pointer;
31+ }
32+
33+ # share-link {
34+ flex-grow : 1 ;
35+ font-family : monospace;
36+ font-size : 12px ;
37+ display : none;
38+ }
39+
40+ # input {
41+ margin-top : 5px ;
42+ width : 100% ;
43+ height : 300px ;
44+ font-family : monospace;
45+ resize : none;
46+ box-sizing : border-box;
47+ }
48+
49+ # output {
50+ margin-top : 5px ;
51+ width : 100% ;
52+ flex-grow : 1 ;
53+ overflow-y : auto;
54+ font-family : monospace;
55+ box-sizing : border-box;
56+ max-height : calc (100vh - 340px );
57+ }
58+
59+ # metadata {
60+ width : 100% ;
61+ font-family : monospace;
62+ font-size : 12px ;
63+ color : # 555 ;
64+ padding : 4px 0 ;
65+ box-sizing : border-box;
66+ }
67+
68+ # metadata span {
69+ margin-right : 12px ;
70+ }
71+ </ style >
72+ < script >
73+ function createTable ( results ) {
74+ const table = document . createElement ( "table" ) ;
75+
76+ if ( results . length === 0 ) return table ;
77+
78+ const headerRow = document . createElement ( "tr" ) ;
79+ Object . entries ( results [ 0 ] ) . forEach ( ( [ key , _ ] ) => {
80+ const th = document . createElement ( "th" ) ;
81+ th . style . textAlign = "left" ;
82+ th . textContent = key ;
83+ headerRow . appendChild ( th ) ;
6584 } ) ;
66- table . appendChild ( tr ) ;
67- } ) ;
6885
69- return table ;
70- }
71-
72- function run ( ) {
73- const input = document . getElementById ( "input" ) . value ;
74- const output = document . getElementById ( "output" ) ;
75- output . innerHTML = "" ;
76- try {
77- const flowquery = new FlowQuery ( input ) ;
78- flowquery . run ( ) . then ( ( ) => {
79- const table = createTable ( flowquery . results ) ;
80- output . appendChild ( table ) ;
81- } ) . catch ( e => {
86+ table . appendChild ( headerRow ) ;
87+
88+ results . forEach ( ( row ) => {
89+ const tr = document . createElement ( "tr" ) ;
90+ Object . entries ( row ) . forEach ( ( [ _ , value ] ) => {
91+ const td = document . createElement ( "td" ) ;
92+ if ( typeof value === "object" || Array . isArray ( value ) ) {
93+ td . textContent = JSON . stringify ( value ) ;
94+ } else {
95+ td . textContent = value ;
96+ }
97+ tr . appendChild ( td ) ;
98+ } ) ;
99+ table . appendChild ( tr ) ;
100+ } ) ;
101+
102+ return table ;
103+ }
104+
105+ function displayMetadata ( metadata ) {
106+ const el = document . getElementById ( "metadata" ) ;
107+ el . innerHTML = "" ;
108+ if ( ! metadata ) return ;
109+ Object . entries ( metadata ) . forEach ( ( [ key , value ] ) => {
110+ const span = document . createElement ( "span" ) ;
111+ span . textContent = key + ": " + value ;
112+ el . appendChild ( span ) ;
113+ } ) ;
114+ }
115+
116+ function run ( ) {
117+ const input = document . getElementById ( "input" ) . value ;
118+ const output = document . getElementById ( "output" ) ;
119+ output . innerHTML = "" ;
120+ displayMetadata ( null ) ;
121+ try {
122+ const flowquery = new FlowQuery ( input ) ;
123+ flowquery
124+ . run ( )
125+ . then ( ( ) => {
126+ const table = createTable ( flowquery . results ) ;
127+ output . appendChild ( table ) ;
128+ displayMetadata ( flowquery . metadata ) ;
129+ } )
130+ . catch ( ( e ) => {
131+ console . error ( e ) ;
132+ output . innerHTML = e . message ;
133+ } ) ;
134+ } catch ( e ) {
82135 console . error ( e ) ;
83136 output . innerHTML = e . message ;
84- } ) ;
85- } catch ( e ) {
86- console . error ( e ) ;
87- output . innerHTML = e . message ;
88- return ;
137+ return ;
138+ }
139+ }
140+
141+ // --- Compression / Decompression using DecompressionStream API ---
142+
143+ async function compressString ( str ) {
144+ const encoder = new TextEncoder ( ) ;
145+ const stream = new Blob ( [ encoder . encode ( str ) ] )
146+ . stream ( )
147+ . pipeThrough ( new CompressionStream ( "deflate-raw" ) ) ;
148+ const compressedBlob = await new Response ( stream ) . blob ( ) ;
149+ const buffer = await compressedBlob . arrayBuffer ( ) ;
150+ // Base64url encode (URL-safe, no padding)
151+ const bytes = new Uint8Array ( buffer ) ;
152+ let binary = "" ;
153+ for ( let i = 0 ; i < bytes . length ; i ++ ) binary += String . fromCharCode ( bytes [ i ] ) ;
154+ return btoa ( binary ) . replace ( / \+ / g, "-" ) . replace ( / \/ / g, "_" ) . replace ( / = + $ / , "" ) ;
155+ }
156+
157+ async function decompressString ( compressed ) {
158+ // Base64url decode
159+ let base64 = compressed . replace ( / - / g, "+" ) . replace ( / _ / g, "/" ) ;
160+ while ( base64 . length % 4 ) base64 += "=" ;
161+ const binary = atob ( base64 ) ;
162+ const bytes = new Uint8Array ( binary . length ) ;
163+ for ( let i = 0 ; i < binary . length ; i ++ ) bytes [ i ] = binary . charCodeAt ( i ) ;
164+
165+ const stream = new Blob ( [ bytes ] )
166+ . stream ( )
167+ . pipeThrough ( new DecompressionStream ( "deflate-raw" ) ) ;
168+ const decompressedBlob = await new Response ( stream ) . blob ( ) ;
169+ return await decompressedBlob . text ( ) ;
170+ }
171+
172+ async function share ( ) {
173+ const input = document . getElementById ( "input" ) . value ;
174+ if ( ! input . trim ( ) ) return ;
175+ const compressed = await compressString ( input ) ;
176+ const url = window . location . origin + window . location . pathname + "?" + compressed ;
177+ const linkEl = document . getElementById ( "share-link" ) ;
178+ linkEl . value = url ;
179+ linkEl . style . display = "block" ;
180+ linkEl . select ( ) ;
181+ navigator . clipboard . writeText ( url ) . catch ( ( ) => { } ) ;
89182 }
90- }
91- </ script >
92- </ head >
183+
184+ // --- Auto-load from URL on page load ---
185+
186+ window . addEventListener ( "DOMContentLoaded" , async ( ) => {
187+ const query = window . location . search ;
188+ if ( query && query . length > 1 ) {
189+ const compressed = query . substring ( 1 ) ; // strip leading '?'
190+ try {
191+ const statement = await decompressString ( compressed ) ;
192+ document . getElementById ( "input" ) . value = statement ;
193+ run ( ) ;
194+ } catch ( e ) {
195+ console . error ( "Failed to decompress URL query:" , e ) ;
196+ }
197+ }
198+ } ) ;
199+ </ script >
200+ </ head >
93201 < body >
94202 < textarea
95203 id ="input "
96204 rows ="10 "
97205 placeholder ="Type your FlowQuery statement here and press Shift+Enter to run it. "
98- onkeydown ="if (event.key === 'Enter' && event.shiftKey) {
99- run();
100- event.preventDefault();
101- } "
206+ onkeydown ="
207+ if (event.key === 'Enter' && event.shiftKey) {
208+ run();
209+ event.preventDefault();
210+ }
211+ "
102212 > </ textarea >
213+ < div id ="toolbar ">
214+ < button onclick ="run() "> Run (Shift+Enter)</ button >
215+ < button onclick ="share() "> Share</ button >
216+ < input id ="share-link " type ="text " readonly onclick ="this.select() " />
217+ </ div >
218+ < div id ="metadata "> </ div >
103219 < div id ="output "> </ div >
104220 </ body >
105- </ html >
221+ </ html >
0 commit comments