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)

sclass LiveInputWeb {
  concept Screen {
    S screenID;
    long lastSeen = now();
    S query;
    transient S status;
    transient Lock lock = lock();
    transient O search;
  
    toString { ret "Screen " + screenID; }
  }

  int statusUpdateInterval = 100;

  transient Map<virtual WebSocket, Screen> webSockets = syncWeakHashMap();

  transient Set<virtual WebSocket> liveSearches = weakHashSet();
  
  void start {
    //dbIndexing(Screen, 'screenID);
    dm_doEvery(statusUpdateInterval, r updateStati);
  }

  set flag NoNanoHTTPD. 
  O html(IWebRequest request) { try {
    try object redirectToHTTPS(request);
    
    S uri = request.uri();
    SS params = request.params();
    print(+params);
    
    S cookie = request.cookie();
    print("cookie", cookie);
    
    new Matches m;
    
    if (eqic(uri, "/about"))
      ret serveText(unindent_mls([[
        What is the BEA Live Input?
  
        Bla bla bla. Also, bla.
      ]]));
    
    if (eqic(uri, "/websockettest")) {
      ret htitle("Web Socket Test")
        + hreconnectingWebSockets()
        + hloadjquery()
        + hdiv("bla<br>", id := "dynamic", style := "font-size: 40px")
        + hjavascript([[
          var ws = new ReconnectingWebSocket("wss://wikify.live/test");
          ws.onopen = function(event) {
            ws.send("here i am");
            $("#dynamic").append("Streaming...<br>");
          };
          ws.onmessage = function(event) {
            $("#dynamic").html(event.data);
            document.title = event.data;
          };
        ]]);
    }

    // home page
    S query = "";
    if (swic(uri, "/search/", m))
      query = takeFirst(1000, urldecode(m.rest()));
    else // now we actually accept wikify.live/{any search term}
      query = urldecode(dropSlashPrefix(uri));

    ret hhtml(hmobilefix() + hhead(
        htitle("BEA Live Input [dev.]")
      + hsansserif()
      + hstyle([[
        .queryContainer {
          height: 100%;
          width: 100%;

          display: flex;
          flex-flow: column;
        }
        
        input.query {
          font-size: 40px;
          padding: 20px;
          width: 400px;
          height: 2em;

          position: relative;
          left: 50%;
          transform: translate(-50%, 0);
          flex: .01 1 auto;
        }

        .filler {
          flex: .5 1 auto;
        }

        .filler.ontop {
          flex: .01 1 auto;
        }
        
        input.query.ontop {
          transform: translate(-50%, 0);
          margin-top: 20px;
          font-size: 20px;
        }
        
        .searching {
          display: none;
        }
        
        .searching.show {
          display: block;
          position: relative;
          left: 50%;
          margin-left: -45px;
        }

        .results {
          font-family: monospace;
          white-space: pre; /* no word-wrapping */
          overflow: auto; /* scrollbars */
          margin-top: 20px;
          border: 2px solid black;
          padding: 0px;
          display: block;
          flex: .01 1 auto;
          opacity: 0%;
          width: 1px;
          margin-left: auto;
          margin-right: auto;
        }

        .results.ontop {
          padding: 20px;
          flex: 1 0 0;  
          opacity: 100%;
          width: calc(100% - 50px);
          left: 0;
          transform: none;
        }
        
        .about {
          margin-bottom: 10px;
          font-size: 12px;
          text-align: center;
          margin-left: auto;
          margin-right: auto;
        }
        
        .status {
          display: none;
          margin-top: 10px;
          font-size: 12px;
        }
        
        .status.ontop {
          display: block;
          margin-left: auto;
          margin-right: auto;
        }
        
        .query, .results, .filler {
          transition: all 1s;
        }
      ]])
      + hLoadJQuery2()
      + hreconnectingWebSockets())
      + hbody(hdiv(
            hdiv("", class := "filler")
          + hdiv("AI bla bla<br>"
          + "By " + ahref("https://BotCompany.de", "BotCompany") + "."
          + targetBlank("/about", "More info."), class := "about")
          + hinput("", value := query, autofocus := true, class := "query", placeholder := "TYPE ANYTHING")
          + hdiv("", class := "status")
          + hdiv(
              hdiv("", class := "actualResults")
            + hdiv(himgsnippet(#1102949, title := "Searching..."), class := "searching"),
              class := "results")
          + hdiv("", class := "filler"),
          class := "queryContainer")
      + hjs_urlencode()
      + hjs([[
        function queryUpdate() {
          var sel = $("input.query, .queryContainer, .results, .filler, .status");
          var q = $("input.query").val();
          if (q == "") {
            $(".actualResults").html("");
            sel.removeClass("ontop");
          } else {
            sel.addClass("ontop");
          }
          if (history.replaceState)
            history.replaceState(null, "Wikify.LIVE", q == "" ? "/" : "/search/" + urlencode(q));
          sendQuery();
        }
      
        $("input.query").on('change keydown paste input', queryUpdate);
        var screenID = Math.random().toString(36).substr(2, 9);
        var wsURL = "wss://wikify.live/search?screen=" + urlencode(screenID);
        console.log("WS: " + wsURL);
        var ws = new ReconnectingWebSocket(wsURL);
        var wsReady = false;
        var querySent = "";
        var maxQueryLength = 1000;

        function wsSend(text) {
          if (wsReady) {
            console.log("wsSend: " + text);
            ws.send(text);
          } else
            console.log("wsNotReady: " + text);
        }

        function sendQuery() {
          var q = $("input.query").val().substring(0, maxQueryLength);
          if (querySent != q) {
            querySent = q;
            wsSend(JSON.stringify({ q: q }));
          }
        }
        
        ws.onopen = function(event) {
          wsReady = true;
          ws.onmessage = function(event) {
            var text = event.data;
            console.log("Got text: " + text);
            var data = JSON.parse(text);
            if ('clearResults' in data)
              $(".actualResults").html("");
            if ('searching' in data)
              $(".searching").addClass("show");
            if ('result' in data)
              $(".actualResults").append(data.result + "<br>");
            if ('searchCancelled' in data)
              $(".searching").removeClass("show");
            if ('searchDone' in data) {
              $(".actualResults").append("<br>[SEARCH DONE]<br>");
              $(".searching").removeClass("show");
            }
            if ('status' in data)
              $(".status").html(data.status);
          };
          queryUpdate();
        };
      ]])
      ));
  } catch e { printStackTrace(e); throw rethrow(e); }
  }
  
  class Request {
    S cookie;  
  }

  void cleanMeUp_webSockets {
    for (virtual WebSocket ws : cloneAndClearKeys(webSockets)) {
      print("Closing WebSocket");
      close((AutoCloseable) ws);
    }
  }

  void cleanMeUp_searches {
    for (virtual WebSocket ws : cloneAndClearList(liveSearches))
      cancelSearch_noLock(webSockets.get(ws));
  }
  
  // API for Eleu
  
  void handleWebSocket(virtual WebSocket ws) {
    print("Got new websocket!");
    set(ws, onClose := r { webSockets.remove(ws) });
    
    set(ws, onOpen := rEnter {  
      S uri = cast rcall getUri(ws);
      
      print("WebSocket opened! uri: " + uri);
      
      SS params = cast rcall getParms(ws);
      S screenID = takeFirst(params.get("screen"), 100);
      Screen screen = uniq Screen(+screenID);
      screen.status = null;
      webSockets.put(ws, screen);
      cset(screen, lastSeen := now());
      
      print("Screen ID: " + screenID);
        
      setFieldToIVF1Proxy(ws, onMessage := msg -> {
        temp enter();
        pcall {
          S text = cast rcall getTextPayload(msg);
          print("Got msg: " + text);

          Map json = jsonDecodeMap(text);
          S q = upper((S) json.get("q"));
          if (q != null && neq(screen.query, q)) {
            temp tempLock("start search", screen.lock);
            
            cancelSearch_noLock(screen);
            cset(screen, query := q);
            if (nempty(q))
              startSearch_noLock(ws, screen, q);
          }
        }
      });

      if (eq(uri, "/test")) thread {
        repeat 100 {
          call(ws, 'send, "It is " + localTimeWithMilliseconds());
          sleep(100);
        }
      }

      if (eq(uri, "/search")) {
      }
    });
  }

  void cancelSearch_noLock(Screen screen) {
    if (screen == null) ret;
    close(screen.search);
    screen.search = null;
  }

  void startSearch_noLock(virtual WebSocket ws, Screen screen, S query) {
    cset(screen, +query);
    call(ws, "send", jsonEncode(litorderedmap("clearResults", true)));
      
    if (empty(query)) ret;
    
    call(ws, "send", jsonEncode(litorderedmap("searching", true)));

    print("Starting search for " + quote(query) + " for " + screen);
    /*LC_ParallelSearch search = new(searchers, query);
    screen.search = search;
    liveSearches.add(ws);
    search.enter = lambda0 enter;
    search.maxResults = defaultResults;
    search.onResult = (iSearcher, iFile, position) -> {
      temp tempLock("onResult", screen.lock);
      if (screen.search != search) ret;
      try {
        //print("Sending result");
        S status = search.status_html();
        call(ws, "send", jsonEncode(litorderedmap(
          result := renderResult(iSearcher, iFile, position, query, screen.search.startTime),
          +status)));
        //print("Sent result");
      } catch print e {
        cancelSearch_noLock(screen);
      }
    };
    search.onSearchDone = r {
      liveSearches.remove(ws);
      print("Search done. " + screen.search + " / " + search);
      temp tempLock("onSearchDone", screen.lock);
      if (screen.search != search) ret;
      updateStatus(ws, screen);
      print("Cancelling search");
      cancelSearch_noLock(screen);
      print("Sending searchDone");
      call(ws, "send", jsonEncode(litorderedmap(
        search.completed() ? "searchDone" : "searchCancelled", true)));
      print("Sent searchDone");
    };
    search.start();*/
  }

  void updateStati enter {
    for (virtual WebSocket ws : cloneList(liveSearches)) pcall {
      Screen screen = webSockets.get(ws);
      updateStatus(ws, screen);
    }
  }
  
  void updateStatus(virtual WebSocket ws, Screen screen) {
    if (screen == null) ret;
    /*LC_ParallelSearch search = screen.search;
    if (search == null) ret;
    S status = search.status_html();
    if (neq(status, screen.status)) {
      screen.status = status;
      call(ws, "send", jsonEncode(litorderedmap(status := screen.status)));
    }*/
  }
}

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: 163 / 181
Version history: 2 change(s)
Referenced in: