Feature Request: The "Search" input searches the contents of files inside of scripts directory

It would be nice if the “Search” field searched all the files in the scripts directory.

I’m having trouble tracking down what app.on/app.off/app.fire things are linked together, and a global file (limited to .js files would be fine) content search would be useful.

A workaround is to download all your scripts and use a text editor to search all files, but it would be nice to be able to do this through the web interface.

Thank you!

Hey @michael,

Indeed that feature would be useful. I could make a JS for that and implement it into a Chrome/Tampermonkey extension if you are interested.

EDIT:

Below is a Tampermonkey script. After a few seconds of loading the editor, you’ll see a new search box(the one on the right is the new one). Use that to search text inside all your scripts. Let me know how it works.

// ==UserScript==
// @name         PlayCanvas Extended
// @namespace    https://playcanvas.com/
// @version      0.1
// @description  New functions for PlayCanvas Editor
// @author       Andrei Vreja
// @match        *://playcanvas.com/editor/scene/*
// @require http://code.jquery.com/jquery-latest.js
// @grant        none
// ==/UserScript==

(function() {
    function WaitForEditor(){
        console.log('trying to start');
        if(typeof config !== "undefined"){
            console.log('starting');
            setTimeout(function() { Start(); }, 5000);
        }
        else{
            setTimeout(function(){
                WaitForEditor();
            }, 250);
        }
    }
    WaitForEditor();

    function Start() {
        var ScriptsPath = 'https://playcanvas.com/api/projects/' + config.project.id + '/repositories/directory/sourcefiles';
        var ScriptsList = [];
        var ScriptsFound = [];
        var Keyword = '';
        var StringCase = false;
        var lastSearch = '';
        var SearchTimer;

        $('.ui-panel.assets>.ui-header').width(800);
        $('.content>.search').clone().insertAfter('.content>.search').addClass('searchinfile');
        $('.searchinfile').attr('data-content', 'Search in scripts');
        $('.searchinfile>.field').on('input change keyup copy paste cut', function() {
            var value = $('.searchinfile>.field').val().trim();

            if (lastSearch === value)
                return;

            lastSearch = value;

            if (value) {
                $('.searchinfile').addClass('not-empty');
            } else {
                $('.searchinfile').removeClass('not-empty');
                editor.call('assets:filter:search', ' ');
                editor.call('assets:filter:search', '');
            }

            if (value) {
                clearTimeout(SearchTimer);
                SearchTimer = setTimeout(function() { Search(value); }, 1000);
            }
        });
        $('.searchinfile>.field').focus(function() {
            $('.searchinfile').addClass('focus');
        });
        $('.searchinfile>.field').focusout(function() {
            $('.searchinfile').removeClass('focus');
        });

        $('.searchinfile>.clear').on('click', function() {
            $('.searchinfile>.field').val('');
            $('.searchinfile').removeClass('not-empty');
            editor.call('assets:filter:search', ' ');
            editor.call('assets:filter:search', '');
        });

        function Search(string) {
            Keyword = string;
            ScriptsList = [];
            ScriptsFound = [];
            var ReqScripts = new XMLHttpRequest();
            ReqScripts.open('GET', ScriptsPath);
            ReqScripts.onreadystatechange = function() {
                if (ReqScripts.readyState == XMLHttpRequest.DONE) {
                    var Scripts = JSON.parse(ReqScripts.responseText)['response'];
                    for (var i = 0; i < Scripts.length; i++) {
                        ScriptsList.push(ScriptsPath + '/' + Scripts[i].filename);
                    }
                    AjaxRequestsMulti(ScriptsList, SearchCallback, SearchCallbackFail);
                }
            };
            editor.call('assets:findOne', function(a) {
                editor.call('assets:panel:get', a['_data']['id']).hidden = true;
            });
            ReqScripts.send();
        }



        SearchCallback = function(data) {
            for (var index in data) {
                if ((StringCase && data[index].indexOf(Keyword) > -1) || (!StringCase && data[index].toLowerCase().indexOf(Keyword.toLowerCase()) > -1)) {
                    console.log('found file: ' + index.split('/').pop());
                    ScriptsFound.push(index.split('/').pop());
                }
            }
            var FindScripts = editor.call('sourcefiles:list');
            for (var i = 0; i < FindScripts.length; i++) {
                if (ScriptsFound.indexOf(FindScripts[i]['_data']['filename']) > -1) {
                    editor.call('assets:panel:get', FindScripts[i]['_data']['filename']).hidden = false;
                } else {
                    editor.call('assets:panel:get', FindScripts[i]['_data']['filename']).hidden = true;
                }
                console.log(FindScripts[i]);
            }
        };

        SearchCallbackFail = function(url) {
            console.log(url + ' failed');
        };

        AjaxRequestExtended = function(url, callback, failCallback) {
            var xmlhttp;

            if (window.XMLHttpRequest)
                xmlhttp = new XMLHttpRequest();
            else
                xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');

            xmlhttp.onreadystatechange = function() {
                if (xmlhttp.readyState == 4) {
                    if (xmlhttp.status == 200)
                        callback(xmlhttp.responseText, url);
                    else
                        failCallback(url);
                }
            };

            xmlhttp.open('GET', url, true);
            xmlhttp.send();
        };

        AjaxRequestsMulti = function(urls, callbackMulti, failCallbackMulti) {
            var isAllCallsCompleted = false;
            var isCallFailed = false;
            var data = {};

            for (var i = 0; i < urls.length; i++) {
                var callback = function(responseText, url) {
                    if (isCallFailed) return;

                    data[url] = responseText;

                    var size = 0;
                    for (var index in data) {
                        if (data.hasOwnProperty(index))
                            size++;
                    }

                    if (size == urls.length)
                        callbackMulti(data);
                };

                var failCallback = function(url) {
                    isCallFailed = true;
                    failCallbackMulti(url);
                };

                AjaxRequestExtended(urls[i], callback, failCallback);
            }
        };
    }
})();
1 Like

Looks like it’s interfering with file uploading(probably with other things too). I’ll fix it tomorrow, in the meanwhile, let me know if what you think about the searching.

That is a feature indeed worth to be implemented, and it is in our backlog.

We will think on UI and workflow for that feature.

Just updated the above script, now it should no longer conflict with the editor.

@max is there any problem with such a script? I am curious about the load on the servers as it loads all the script files on input change on the new search box; but well, the editor is already communicating with the servers a lot.

EDIT:

Changed the script again, added a simple timer so it won’t load all scripts at every input change, instead, load them 1 second after input. This way you can type a word/phrase and it will only send the request when you’re “done typing”.

There are many things can be done.
First of all in most cases it is possible to store all scripts in memory, it is rarely going to be too much data to store.
There are ways to identify if script been changed. Actually whole editor has own API, there are ways to identify if script been changed, and if it was, then mark it “dirty” next time when search required it will reload only dirty ones.

This way it will not spam server loading script files, and search will be faster.

Indeed, that would be a good idea. I wanted to do something similar, but I kinda failed accessing most of the Editor’s API. I am new to constructors and such stuff. Maybe one day I will finish this search script, along with other features.

I give you some hints on Editor’s API:
There is a global variable editor, and in launcher it is app.
It has some interface, mostly:
call(name, args..) - calls API method.
emit(name, args..) - emits API event.
on/once(name, function(args..) { }) - subscribe to API event.

Then check out this:
editor._hooks - this object contains list of all methods (hooks) that are accessible through editor.call method. There are 316 of them (28/06/16). And they all are different API’s.

Then there is a concept of “Observer” which is an object that has JSON representation, but is accessible through get/set and has events on any changes like: entity.on('position.0:set', function(x) { }); - will fire every time entities X is changed.
Whole UI and live-sync is linked to it. So changing that object, will be reflected everywhere in UI, viewport and will be saved.

So to get list of scripts of a project and their full paths as array:

var scriptPaths = editor.call('sourcefiles:list').map(function(script) {
    var filename = script.get('filename');
    var path = editor.call('sourcefiles:url', filename);
    return path
});

Please remember, there is a good amount of work going on right now on scripts. They still are sourcefiles, but this is about to change for new projects, as we introducing new system where scripts are assets, and it will have all benefits of assets: ability to know when script asset been changed, so that you can do caching etc.

Editor’s API is not documented anywhere, and we have plans to tidy it up and start opening it up, but it requires a good system around it to make community able to make and share Editor Plugins securely.
So use for your own risk :slight_smile: