Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

384
LINES

< > BotCompany Repo | #1030842 // LiveInputWeb [embeddable WebSocket-based live search, dev.]

JavaX fragment (include)

1  
sclass LiveInputWeb {
2  
  concept Screen {
3  
    S screenID;
4  
    long lastSeen = now();
5  
    S query;
6  
    transient S status;
7  
    transient Lock lock = lock();
8  
    transient O search;
9  
  
10  
    toString { ret "Screen " + screenID; }
11  
  }
12  
13  
  int statusUpdateInterval = 100;
14  
15  
  transient Map<virtual WebSocket, Screen> webSockets = syncWeakHashMap();
16  
17  
  transient Set<virtual WebSocket> liveSearches = weakHashSet();
18  
  
19  
  void start {
20  
    //dbIndexing(Screen, 'screenID);
21  
    dm_doEvery(statusUpdateInterval, r updateStati);
22  
  }
23  
24  
  set flag NoNanoHTTPD. 
25  
  O html(IWebRequest request) { try {
26  
    try object redirectToHTTPS(request);
27  
    
28  
    S uri = request.uri();
29  
    SS params = request.params();
30  
    print(+params);
31  
    
32  
    S cookie = request.cookie();
33  
    print("cookie", cookie);
34  
    
35  
    new Matches m;
36  
    
37  
    if (eqic(uri, "/about"))
38  
      ret serveText(unindent_mls([[
39  
        What is the BEA Live Input?
40  
  
41  
        Bla bla bla. Also, bla.
42  
      ]]));
43  
    
44  
    if (eqic(uri, "/websockettest")) {
45  
      ret htitle("Web Socket Test")
46  
        + hreconnectingWebSockets()
47  
        + hloadjquery()
48  
        + hdiv("bla<br>", id := "dynamic", style := "font-size: 40px")
49  
        + hjavascript([[
50  
          var ws = new ReconnectingWebSocket("wss://wikify.live/test");
51  
          ws.onopen = function(event) {
52  
            ws.send("here i am");
53  
            $("#dynamic").append("Streaming...<br>");
54  
          };
55  
          ws.onmessage = function(event) {
56  
            $("#dynamic").html(event.data);
57  
            document.title = event.data;
58  
          };
59  
        ]]);
60  
    }
61  
62  
    // home page
63  
    S query = "";
64  
    if (swic(uri, "/search/", m))
65  
      query = takeFirst(1000, urldecode(m.rest()));
66  
    else // now we actually accept wikify.live/{any search term}
67  
      query = urldecode(dropSlashPrefix(uri));
68  
69  
    ret hhtml(hmobilefix() + hhead(
70  
        htitle("BEA Live Input [dev.]")
71  
      + hsansserif()
72  
      + hstyle([[
73  
        .queryContainer {
74  
          height: 100%;
75  
          width: 100%;
76  
77  
          display: flex;
78  
          flex-flow: column;
79  
        }
80  
        
81  
        input.query {
82  
          font-size: 40px;
83  
          padding: 20px;
84  
          width: 400px;
85  
          height: 2em;
86  
87  
          position: relative;
88  
          left: 50%;
89  
          transform: translate(-50%, 0);
90  
          flex: .01 1 auto;
91  
        }
92  
93  
        .filler {
94  
          flex: .5 1 auto;
95  
        }
96  
97  
        .filler.ontop {
98  
          flex: .01 1 auto;
99  
        }
100  
        
101  
        input.query.ontop {
102  
          transform: translate(-50%, 0);
103  
          margin-top: 20px;
104  
          font-size: 20px;
105  
        }
106  
        
107  
        .searching {
108  
          display: none;
109  
        }
110  
        
111  
        .searching.show {
112  
          display: block;
113  
          position: relative;
114  
          left: 50%;
115  
          margin-left: -45px;
116  
        }
117  
118  
        .results {
119  
          font-family: monospace;
120  
          white-space: pre; /* no word-wrapping */
121  
          overflow: auto; /* scrollbars */
122  
          margin-top: 20px;
123  
          border: 2px solid black;
124  
          padding: 0px;
125  
          display: block;
126  
          flex: .01 1 auto;
127  
          opacity: 0%;
128  
          width: 1px;
129  
          margin-left: auto;
130  
          margin-right: auto;
131  
        }
132  
133  
        .results.ontop {
134  
          padding: 20px;
135  
          flex: 1 0 0;  
136  
          opacity: 100%;
137  
          width: calc(100% - 50px);
138  
          left: 0;
139  
          transform: none;
140  
        }
141  
        
142  
        .about {
143  
          margin-bottom: 10px;
144  
          font-size: 12px;
145  
          text-align: center;
146  
          margin-left: auto;
147  
          margin-right: auto;
148  
        }
149  
        
150  
        .status {
151  
          display: none;
152  
          margin-top: 10px;
153  
          font-size: 12px;
154  
        }
155  
        
156  
        .status.ontop {
157  
          display: block;
158  
          margin-left: auto;
159  
          margin-right: auto;
160  
        }
161  
        
162  
        .query, .results, .filler {
163  
          transition: all 1s;
164  
        }
165  
      ]])
166  
      + hLoadJQuery2()
167  
      + hreconnectingWebSockets())
168  
      + hbody(hdiv(
169  
            hdiv("", class := "filler")
170  
          + hdiv("AI bla bla<br>"
171  
          + "By " + ahref("https://BotCompany.de", "BotCompany") + "."
172  
          + targetBlank("/about", "More info."), class := "about")
173  
          + hinput("", value := query, autofocus := true, class := "query", placeholder := "TYPE ANYTHING")
174  
          + hdiv("", class := "status")
175  
          + hdiv(
176  
              hdiv("", class := "actualResults")
177  
            + hdiv(himgsnippet(#1102949, title := "Searching..."), class := "searching"),
178  
              class := "results")
179  
          + hdiv("", class := "filler"),
180  
          class := "queryContainer")
181  
      + hjs_urlencode()
182  
      + hjs([[
183  
        function queryUpdate() {
184  
          var sel = $("input.query, .queryContainer, .results, .filler, .status");
185  
          var q = $("input.query").val();
186  
          if (q == "") {
187  
            $(".actualResults").html("");
188  
            sel.removeClass("ontop");
189  
          } else {
190  
            sel.addClass("ontop");
191  
          }
192  
          if (history.replaceState)
193  
            history.replaceState(null, "Wikify.LIVE", q == "" ? "/" : "/search/" + urlencode(q));
194  
          sendQuery();
195  
        }
196  
      
197  
        $("input.query").on('change keydown paste input', queryUpdate);
198  
        var screenID = Math.random().toString(36).substr(2, 9);
199  
        var wsURL = "wss://wikify.live/search?screen=" + urlencode(screenID);
200  
        console.log("WS: " + wsURL);
201  
        var ws = new ReconnectingWebSocket(wsURL);
202  
        var wsReady = false;
203  
        var querySent = "";
204  
        var maxQueryLength = 1000;
205  
206  
        function wsSend(text) {
207  
          if (wsReady) {
208  
            console.log("wsSend: " + text);
209  
            ws.send(text);
210  
          } else
211  
            console.log("wsNotReady: " + text);
212  
        }
213  
214  
        function sendQuery() {
215  
          var q = $("input.query").val().substring(0, maxQueryLength);
216  
          if (querySent != q) {
217  
            querySent = q;
218  
            wsSend(JSON.stringify({ q: q }));
219  
          }
220  
        }
221  
        
222  
        ws.onopen = function(event) {
223  
          wsReady = true;
224  
          ws.onmessage = function(event) {
225  
            var text = event.data;
226  
            console.log("Got text: " + text);
227  
            var data = JSON.parse(text);
228  
            if ('clearResults' in data)
229  
              $(".actualResults").html("");
230  
            if ('searching' in data)
231  
              $(".searching").addClass("show");
232  
            if ('result' in data)
233  
              $(".actualResults").append(data.result + "<br>");
234  
            if ('searchCancelled' in data)
235  
              $(".searching").removeClass("show");
236  
            if ('searchDone' in data) {
237  
              $(".actualResults").append("<br>[SEARCH DONE]<br>");
238  
              $(".searching").removeClass("show");
239  
            }
240  
            if ('status' in data)
241  
              $(".status").html(data.status);
242  
          };
243  
          queryUpdate();
244  
        };
245  
      ]])
246  
      ));
247  
  } catch e { printStackTrace(e); throw rethrow(e); }
248  
  }
249  
  
250  
  class Request {
251  
    S cookie;  
252  
  }
253  
254  
  void cleanMeUp_webSockets {
255  
    for (virtual WebSocket ws : cloneAndClearKeys(webSockets)) {
256  
      print("Closing WebSocket");
257  
      close((AutoCloseable) ws);
258  
    }
259  
  }
260  
261  
  void cleanMeUp_searches {
262  
    for (virtual WebSocket ws : cloneAndClearList(liveSearches))
263  
      cancelSearch_noLock(webSockets.get(ws));
264  
  }
265  
  
266  
  // API for Eleu
267  
  
268  
  void handleWebSocket(virtual WebSocket ws) {
269  
    print("Got new websocket!");
270  
    set(ws, onClose := r { webSockets.remove(ws) });
271  
    
272  
    set(ws, onOpen := rEnter {  
273  
      S uri = cast rcall getUri(ws);
274  
      
275  
      print("WebSocket opened! uri: " + uri);
276  
      
277  
      SS params = cast rcall getParms(ws);
278  
      S screenID = takeFirst(params.get("screen"), 100);
279  
      Screen screen = uniq Screen(+screenID);
280  
      screen.status = null;
281  
      webSockets.put(ws, screen);
282  
      cset(screen, lastSeen := now());
283  
      
284  
      print("Screen ID: " + screenID);
285  
        
286  
      setFieldToIVF1Proxy(ws, onMessage := msg -> {
287  
        temp enter();
288  
        pcall {
289  
          S text = cast rcall getTextPayload(msg);
290  
          print("Got msg: " + text);
291  
292  
          Map json = jsonDecodeMap(text);
293  
          S q = upper((S) json.get("q"));
294  
          if (q != null && neq(screen.query, q)) {
295  
            temp tempLock("start search", screen.lock);
296  
            
297  
            cancelSearch_noLock(screen);
298  
            cset(screen, query := q);
299  
            if (nempty(q))
300  
              startSearch_noLock(ws, screen, q);
301  
          }
302  
        }
303  
      });
304  
305  
      if (eq(uri, "/test")) thread {
306  
        repeat 100 {
307  
          call(ws, 'send, "It is " + localTimeWithMilliseconds());
308  
          sleep(100);
309  
        }
310  
      }
311  
312  
      if (eq(uri, "/search")) {
313  
      }
314  
    });
315  
  }
316  
317  
  void cancelSearch_noLock(Screen screen) {
318  
    if (screen == null) ret;
319  
    close(screen.search);
320  
    screen.search = null;
321  
  }
322  
323  
  void startSearch_noLock(virtual WebSocket ws, Screen screen, S query) {
324  
    cset(screen, +query);
325  
    call(ws, "send", jsonEncode(litorderedmap("clearResults", true)));
326  
      
327  
    if (empty(query)) ret;
328  
    
329  
    call(ws, "send", jsonEncode(litorderedmap("searching", true)));
330  
331  
    print("Starting search for " + quote(query) + " for " + screen);
332  
    /*LC_ParallelSearch search = new(searchers, query);
333  
    screen.search = search;
334  
    liveSearches.add(ws);
335  
    search.enter = lambda0 enter;
336  
    search.maxResults = defaultResults;
337  
    search.onResult = (iSearcher, iFile, position) -> {
338  
      temp tempLock("onResult", screen.lock);
339  
      if (screen.search != search) ret;
340  
      try {
341  
        //print("Sending result");
342  
        S status = search.status_html();
343  
        call(ws, "send", jsonEncode(litorderedmap(
344  
          result := renderResult(iSearcher, iFile, position, query, screen.search.startTime),
345  
          +status)));
346  
        //print("Sent result");
347  
      } catch print e {
348  
        cancelSearch_noLock(screen);
349  
      }
350  
    };
351  
    search.onSearchDone = r {
352  
      liveSearches.remove(ws);
353  
      print("Search done. " + screen.search + " / " + search);
354  
      temp tempLock("onSearchDone", screen.lock);
355  
      if (screen.search != search) ret;
356  
      updateStatus(ws, screen);
357  
      print("Cancelling search");
358  
      cancelSearch_noLock(screen);
359  
      print("Sending searchDone");
360  
      call(ws, "send", jsonEncode(litorderedmap(
361  
        search.completed() ? "searchDone" : "searchCancelled", true)));
362  
      print("Sent searchDone");
363  
    };
364  
    search.start();*/
365  
  }
366  
367  
  void updateStati enter {
368  
    for (virtual WebSocket ws : cloneList(liveSearches)) pcall {
369  
      Screen screen = webSockets.get(ws);
370  
      updateStatus(ws, screen);
371  
    }
372  
  }
373  
  
374  
  void updateStatus(virtual WebSocket ws, Screen screen) {
375  
    if (screen == null) ret;
376  
    /*LC_ParallelSearch search = screen.search;
377  
    if (search == null) ret;
378  
    S status = search.status_html();
379  
    if (neq(status, screen.status)) {
380  
      screen.status = status;
381  
      call(ws, "send", jsonEncode(litorderedmap(status := screen.status)));
382  
    }*/
383  
  }
384  
}

Author comment

Began life as a copy of #1030819

download  show line numbers  debug dex  old transpilations   

Travelled to 4 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, vouqrxazstgt

No comments. add comment

Snippet ID: #1030842
Snippet name: LiveInputWeb [embeddable WebSocket-based live search, dev.]
Eternal ID of this version: #1030842/3
Text MD5: cde6489243c2d8212868508db06df82e
Author: stefan
Category: javax
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2021-04-02 15:06:28
Source code size: 11332 bytes / 384 lines
Pitched / IR pitched: No / No
Views / Downloads: 164 / 182
Version history: 2 change(s)
Referenced in: [show references]