New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[enhancement] - Don't clear terminal on resize #148

Closed
drewbaker opened this Issue Feb 25, 2014 · 20 comments

Comments

Projects
None yet
2 participants
@drewbaker

drewbaker commented Feb 25, 2014

Hey Jakob,

It would be great to be able to set resize:false, and have the terminal not clear it's contents on resize.

I'm loading in content to the divs created by an empty echo using PJAX, and they get cleared by resize function.

Thanks,
Drew

@jcubic

This comment has been minimized.

Show comment
Hide comment
@jcubic

jcubic Feb 25, 2014

Owner

Can you show code (or demo) where you use the terminal? And what is PJAX?

Owner

jcubic commented Feb 25, 2014

Can you show code (or demo) where you use the terminal? And what is PJAX?

@drewbaker

This comment has been minimized.

Show comment
Hide comment
@drewbaker

drewbaker Mar 22, 2014

Hey Jakob,

Sorry to lag on this! I built you a demo of PJAX working with terminal, but it clears when you resize the window.
http://labs.funkhausdesign.com/examples/terminal/terminal_pjax.html

PJAX is this:
https://github.com/defunkt/jquery-pjax

It's a great way of using HTML5 pushState tech to have no page loads, but have deep linkable URL's.

If you send me your email address, I can show you the development site that I have this tech working on for one of our clients. It's a super advanced version of Terminal using PJAX. My email is drew@funkhaus.us.

Thanks!

drewbaker commented Mar 22, 2014

Hey Jakob,

Sorry to lag on this! I built you a demo of PJAX working with terminal, but it clears when you resize the window.
http://labs.funkhausdesign.com/examples/terminal/terminal_pjax.html

PJAX is this:
https://github.com/defunkt/jquery-pjax

It's a great way of using HTML5 pushState tech to have no page loads, but have deep linkable URL's.

If you send me your email address, I can show you the development site that I have this tech working on for one of our clients. It's a super advanced version of Terminal using PJAX. My email is drew@funkhaus.us.

Thanks!

@jcubic

This comment has been minimized.

Show comment
Hide comment
@jcubic

jcubic Mar 22, 2014

Owner

You're not using terminal and Pjax as it should be used. As for terminal use api provided. As for History API I sugest differnt library that alow for callbacks. Also in your code you don't have full change on back button only url and image change the command entered stay the same.

In order to have proper state you need to use export_view / import_view functions. Here is the code I've came up with (that use HTML5-History-API library which is just a polyfill so it use raw HistoryAPI)

$(function() {
    var save_state = [];
    var terminal = $('#term').terminal(function(command, term) {
        var cmd = $.terminal.splitCommand(command);
        if (cmd.name == 'open') {
            term.pause();
            $.get(cmd.args[0], function(result) {
                term.echo(result, {raw:true}).resume();
                save_state.push(term.export_view());
                history.pushState(save_state.length-1, null, cmd.args[0]);
            }, 'text');
        } else {
            // store all other commands
            save_state.push(term.export_view());
            history.pushState(save_state.length-1, null, '/' + cmd.name + '/' + cmd.args.join('/'));
        }
    });
    save_state.push(terminal.export_view());
    $(window).on('popstate', function(e) {
        if (save_state.length) {
            terminal.import_view(save_state[history.state || 0]);
        }
    });
    $('.links a').click(function(e) {
        terminal.exec('open "' + $(this).attr('href') + '"');
        return false;
    });
});

save_state store exported view of the terminal and index in history.state. If history.state is null it mean that it's first page so code can grab 0 index (that exported after terminal creation).
The above command store in History API all commands that you type into the terminal. (I think that I put that code on examples).

Owner

jcubic commented Mar 22, 2014

You're not using terminal and Pjax as it should be used. As for terminal use api provided. As for History API I sugest differnt library that alow for callbacks. Also in your code you don't have full change on back button only url and image change the command entered stay the same.

In order to have proper state you need to use export_view / import_view functions. Here is the code I've came up with (that use HTML5-History-API library which is just a polyfill so it use raw HistoryAPI)

$(function() {
    var save_state = [];
    var terminal = $('#term').terminal(function(command, term) {
        var cmd = $.terminal.splitCommand(command);
        if (cmd.name == 'open') {
            term.pause();
            $.get(cmd.args[0], function(result) {
                term.echo(result, {raw:true}).resume();
                save_state.push(term.export_view());
                history.pushState(save_state.length-1, null, cmd.args[0]);
            }, 'text');
        } else {
            // store all other commands
            save_state.push(term.export_view());
            history.pushState(save_state.length-1, null, '/' + cmd.name + '/' + cmd.args.join('/'));
        }
    });
    save_state.push(terminal.export_view());
    $(window).on('popstate', function(e) {
        if (save_state.length) {
            terminal.import_view(save_state[history.state || 0]);
        }
    });
    $('.links a').click(function(e) {
        terminal.exec('open "' + $(this).attr('href') + '"');
        return false;
    });
});

save_state store exported view of the terminal and index in history.state. If history.state is null it mean that it's first page so code can grab 0 index (that exported after terminal creation).
The above command store in History API all commands that you type into the terminal. (I think that I put that code on examples).

@jcubic

This comment has been minimized.

Show comment
Hide comment
@jcubic

jcubic Mar 22, 2014

Owner

I can add this to the terminal, it can store state just before next command be executed and it can store urls based on user function:

$('...').terminal(function(cmd, term) {
}, {
   historyState: true,
   historyStateUrlMapper: function(command) {
       // whatever that function return, will be stored as url, default will be
       // default can be "real url" + command.replace(/\s+/g, '/');
       // if the value is null the url will not be stored, so it will act as filter.
   }
});

So your code in this case will be just:

$('...').terminal({
    open: function(url) {
        var self = this.pause();
        $.get(url, function(result) {
            self.echo(result, {raw:true}).resume();
        }, 'text');
    }
}, {
   historyState: true,
   historyStateUrlMapper: function(command) {
       if (command.match(/^open/)) {
           return command.replace(/^[^\s]+\s*/, '');
       }
   }
});
Owner

jcubic commented Mar 22, 2014

I can add this to the terminal, it can store state just before next command be executed and it can store urls based on user function:

$('...').terminal(function(cmd, term) {
}, {
   historyState: true,
   historyStateUrlMapper: function(command) {
       // whatever that function return, will be stored as url, default will be
       // default can be "real url" + command.replace(/\s+/g, '/');
       // if the value is null the url will not be stored, so it will act as filter.
   }
});

So your code in this case will be just:

$('...').terminal({
    open: function(url) {
        var self = this.pause();
        $.get(url, function(result) {
            self.echo(result, {raw:true}).resume();
        }, 'text');
    }
}, {
   historyState: true,
   historyStateUrlMapper: function(command) {
       if (command.match(/^open/)) {
           return command.replace(/^[^\s]+\s*/, '');
       }
   }
});
@drewbaker

This comment has been minimized.

Show comment
Hide comment
@drewbaker

drewbaker Mar 22, 2014

That second historyState code is great. I can see that being very useful. I will look into using that. Are you going to bake that into the main Terminal code, or is this just for my use case?

But is it possible to somehow disable Terminal from clearing all content on resize? Even just point me to the right place in the code, so I can hack it for my needs right now.

I agree that PJAX isn't great, I do wish it had proper callbacks and could return data, rather than forcing it to go to a selector.

Thanks!

drewbaker commented Mar 22, 2014

That second historyState code is great. I can see that being very useful. I will look into using that. Are you going to bake that into the main Terminal code, or is this just for my use case?

But is it possible to somehow disable Terminal from clearing all content on resize? Even just point me to the right place in the code, so I can hack it for my needs right now.

I agree that PJAX isn't great, I do wish it had proper callbacks and could return data, rather than forcing it to go to a selector.

Thanks!

@jcubic

This comment has been minimized.

Show comment
Hide comment
@jcubic

jcubic Mar 22, 2014

Owner

I can make that historyState into terminal 0.9, nice feature, I don't have much features to add to new version of terminal.

to disable clear in the terminal you need to comment out that line:
https://github.com/jcubic/jquery.terminal/blob/master/js/jquery.terminal-src.js#L3815
it will never redraw terminal, but you will not have line wrapping. Clear is used to draw lines (proper lines that was echo using echo function) again.

Owner

jcubic commented Mar 22, 2014

I can make that historyState into terminal 0.9, nice feature, I don't have much features to add to new version of terminal.

to disable clear in the terminal you need to comment out that line:
https://github.com/jcubic/jquery.terminal/blob/master/js/jquery.terminal-src.js#L3815
it will never redraw terminal, but you will not have line wrapping. Clear is used to draw lines (proper lines that was echo using echo function) again.

@drewbaker

This comment has been minimized.

Show comment
Hide comment
@drewbaker

drewbaker Apr 8, 2014

Hey Jakob,

Any ideas on when you might release 0.9? I want to start using this History feature on our project.

Thanks!
Drew

drewbaker commented Apr 8, 2014

Hey Jakob,

Any ideas on when you might release 0.9? I want to start using this History feature on our project.

Thanks!
Drew

@jcubic

This comment has been minimized.

Show comment
Hide comment
@jcubic

jcubic Apr 8, 2014

Owner

I can add that history feature when I find some time, and then you will be able to grab it from devel branch.

Owner

jcubic commented Apr 8, 2014

I can add that history feature when I find some time, and then you will be able to grab it from devel branch.

@jcubic

This comment has been minimized.

Show comment
Hide comment
@jcubic

jcubic Apr 10, 2014

Owner

I've added the feature to devel branch.

Owner

jcubic commented Apr 10, 2014

I've added the feature to devel branch.

@jcubic jcubic closed this Apr 10, 2014

@drewbaker

This comment has been minimized.

Show comment
Hide comment
@drewbaker

drewbaker Apr 11, 2014

Hey Jakob,

I've been using this, and it's great! Quick question:

Say I echo some HTML via AJAX like you have above. Let's say its a grid of thumbnail images, and set the state via your new feature.

Next, I click on the thumbnail, and via AJAX I replace that thumbnail with a video. How do you recommend I deal with that state change? I can't echo out a new command, because I want that video to replace the image, not echo onto a new line.

Currently I'm just setting that history.pushState() manually on click, but I was wondering if I could do it smarter through this new Terminal feature you added?

Thanks again! This is a great plugin!

drewbaker commented Apr 11, 2014

Hey Jakob,

I've been using this, and it's great! Quick question:

Say I echo some HTML via AJAX like you have above. Let's say its a grid of thumbnail images, and set the state via your new feature.

Next, I click on the thumbnail, and via AJAX I replace that thumbnail with a video. How do you recommend I deal with that state change? I can't echo out a new command, because I want that video to replace the image, not echo onto a new line.

Currently I'm just setting that history.pushState() manually on click, but I was wondering if I could do it smarter through this new Terminal feature you added?

Thanks again! This is a great plugin!

@jcubic

This comment has been minimized.

Show comment
Hide comment
@jcubic

jcubic Apr 11, 2014

Owner

You don't do that with terminal. Maybe I can pushState on import_view and add flag that will prevent that to that function so popstate will be able to use import without pushing state.

Do you use import/export to change image to video? Because that is the only way to change lines dynamically that will keep terminal internal state.

Owner

jcubic commented Apr 11, 2014

You don't do that with terminal. Maybe I can pushState on import_view and add flag that will prevent that to that function so popstate will be able to use import without pushing state.

Do you use import/export to change image to video? Because that is the only way to change lines dynamically that will keep terminal internal state.

@drewbaker

This comment has been minimized.

Show comment
Hide comment
@drewbaker

drewbaker Apr 11, 2014

I don't currently use import/export. I didn't think that would let me dynamically change part of a page?

drewbaker commented Apr 11, 2014

I don't currently use import/export. I didn't think that would let me dynamically change part of a page?

@jcubic

This comment has been minimized.

Show comment
Hide comment
@jcubic

jcubic Apr 11, 2014

Owner

Export give you access to internal array of lines, that are redraw on refresh. I can prepare demo how to modify lines.

Owner

jcubic commented Apr 11, 2014

Export give you access to internal array of lines, that are redraw on refresh. I can prepare demo how to modify lines.

@drewbaker

This comment has been minimized.

Show comment
Hide comment
@drewbaker

drewbaker Apr 11, 2014

Thanks! It would be great to see an example of export, change content, then import it back into view.

drewbaker commented Apr 11, 2014

Thanks! It would be great to see an example of export, change content, then import it back into view.

@drewbaker

This comment has been minimized.

Show comment
Hide comment
@drewbaker

drewbaker Apr 12, 2014

I've been playing with this today.

I've made it so that, on click of an image, I export the view, and then I can use the HTML and edit it.

But I struggle to then import it back to the correct line. The image clicked might be 3 lines previous.

drewbaker commented Apr 12, 2014

I've been playing with this today.

I've made it so that, on click of an image, I export the view, and then I can use the HTML and edit it.

But I struggle to then import it back to the correct line. The image clicked might be 3 lines previous.

@jcubic

This comment has been minimized.

Show comment
Hide comment
@jcubic

jcubic Apr 12, 2014

Owner

The code will be:

    $('.selector').terminal(function(cmd, term) {
        if (cmd == 'foo') {
            term.echo('<img src="/some/image.png" data-replace="/some/other/image.png">', {
                raw: true
            });
        }
    }).on('click', 'img', function() {
        var self = $(this);
        // get index of the line you just click on
        var index = self.parents('.terminal-output > div').index();
        // you can also use $.terminal.active()
        var terminal = self.parents('.terminal').terminal();
        var view = terminal.export_view();
        var line = view.lines[index][0]; // index 0 is a string 1 is options for the line
        view.lines[index][0] = line.replace(self.attr('src'), self.data('replace'));
        terminal.import_view(view).save_state();
    });

I came into a problem that lines in export/import was using shallow copy (lines.slice(0)) and when you modify what you exported you where changing old state, I fix that in last commit (save_state() is new API function I've added, it push history state and export_view() so you can call it if you do something outside of terminal like the click event here).

Owner

jcubic commented Apr 12, 2014

The code will be:

    $('.selector').terminal(function(cmd, term) {
        if (cmd == 'foo') {
            term.echo('<img src="/some/image.png" data-replace="/some/other/image.png">', {
                raw: true
            });
        }
    }).on('click', 'img', function() {
        var self = $(this);
        // get index of the line you just click on
        var index = self.parents('.terminal-output > div').index();
        // you can also use $.terminal.active()
        var terminal = self.parents('.terminal').terminal();
        var view = terminal.export_view();
        var line = view.lines[index][0]; // index 0 is a string 1 is options for the line
        view.lines[index][0] = line.replace(self.attr('src'), self.data('replace'));
        terminal.import_view(view).save_state();
    });

I came into a problem that lines in export/import was using shallow copy (lines.slice(0)) and when you modify what you exported you where changing old state, I fix that in last commit (save_state() is new API function I've added, it push history state and export_view() so you can call it if you do something outside of terminal like the click event here).

@drewbaker

This comment has been minimized.

Show comment
Hide comment
@drewbaker

drewbaker Apr 14, 2014

If I use save_state() like you have it on the last line above, what URL does it save in the pushState?

drewbaker commented Apr 14, 2014

If I use save_state() like you have it on the last line above, what URL does it save in the pushState?

@jcubic

This comment has been minimized.

Show comment
Hide comment
@jcubic

jcubic Apr 14, 2014

Owner

In my case none, but you can pass arguments to that function save_state(title, url). (I think that title is not used at all by the browsers).

Owner

jcubic commented Apr 14, 2014

In my case none, but you can pass arguments to that function save_state(title, url). (I think that title is not used at all by the browsers).

@jcubic

This comment has been minimized.

Show comment
Hide comment
@jcubic

jcubic May 30, 2014

Owner

If you're interested, I think I remove historyState and use hash change, it's much better because it keep state when you refresh. Here is the code, right now outside of terminal:

$(function() {
    var state = [];
    var save = false; // don't change hash onInit
    var process_hash = true;
    var terminal = $('body').terminal($.noop, {
        onAfterCommand: function(term, command) {
            if (!$.isArray(commands)) {
                commands = [];
            }
            state.push(term.export_view());
            commands.push([state.length-1, command]);
            if (save) {
                // don't call hashchange on this change
                process_hash = false;
                location.hash = JSON.stringify(commands);
                setTimeout(function() {
                    process_hash = true;
                }, 100);
            }
        },
        onInit: function(term) {
            state.push(term.export_view());
        },
        onBlur: function() {
            return false;
        }
    });

    $(window).hashchange(function() {
        if (process_hash) {
            try {
                if (location.hash) {
                    commands = $.parseJSON(location.hash.replace(/^#/, ''));
                } else {
                    commands = [];
                }
                if (commands.length) {
                    var json_state = commands[commands.length-1];
                    if (state[json_state[0]]) {
                        terminal.import_view(state[json_state[0]]);
                    } else {
                        // don't change hash with this exec
                        save = false;
                        terminal.exec(json_state[1]);
                        // undocumented terminal fire this event
                        terminal.bind('resume.hash', function() {
                            save = true;
                            terminal.unbind('resume.hash');
                        });
                    }
                } else if (state[0]) {
                    terminal.import_view(state[0]);
                }
            } catch(e) {
                terminal.exception(e);
            }
        }
    });

    var commands;
    function exec_hash() {
        if (location.hash) {
            try {
                commands = $.parseJSON(location.hash.replace(/^#/, ''));
                $.each(commands, function(i, command) {
                    try {
                        terminal.exec(command[1]);
                    } catch(e) {
                        var cmd = $.terminal.escape_brackets(command);
                        var msg = "Error while exec with command " + cmd;
                        terminal.error(msg).error(e.stack);
                    }
                });
            } catch (e) {
                //invalid json - ignore
            }
        }
    }
    exec_hash();
    save = true;
});

It use jQuery hashchange and I've added onAfterCommand callback (I dind't push onAfterCommand into devel branch yet). I not sure if I should put that code into terminal. Maybe I should just put it as example.

Owner

jcubic commented May 30, 2014

If you're interested, I think I remove historyState and use hash change, it's much better because it keep state when you refresh. Here is the code, right now outside of terminal:

$(function() {
    var state = [];
    var save = false; // don't change hash onInit
    var process_hash = true;
    var terminal = $('body').terminal($.noop, {
        onAfterCommand: function(term, command) {
            if (!$.isArray(commands)) {
                commands = [];
            }
            state.push(term.export_view());
            commands.push([state.length-1, command]);
            if (save) {
                // don't call hashchange on this change
                process_hash = false;
                location.hash = JSON.stringify(commands);
                setTimeout(function() {
                    process_hash = true;
                }, 100);
            }
        },
        onInit: function(term) {
            state.push(term.export_view());
        },
        onBlur: function() {
            return false;
        }
    });

    $(window).hashchange(function() {
        if (process_hash) {
            try {
                if (location.hash) {
                    commands = $.parseJSON(location.hash.replace(/^#/, ''));
                } else {
                    commands = [];
                }
                if (commands.length) {
                    var json_state = commands[commands.length-1];
                    if (state[json_state[0]]) {
                        terminal.import_view(state[json_state[0]]);
                    } else {
                        // don't change hash with this exec
                        save = false;
                        terminal.exec(json_state[1]);
                        // undocumented terminal fire this event
                        terminal.bind('resume.hash', function() {
                            save = true;
                            terminal.unbind('resume.hash');
                        });
                    }
                } else if (state[0]) {
                    terminal.import_view(state[0]);
                }
            } catch(e) {
                terminal.exception(e);
            }
        }
    });

    var commands;
    function exec_hash() {
        if (location.hash) {
            try {
                commands = $.parseJSON(location.hash.replace(/^#/, ''));
                $.each(commands, function(i, command) {
                    try {
                        terminal.exec(command[1]);
                    } catch(e) {
                        var cmd = $.terminal.escape_brackets(command);
                        var msg = "Error while exec with command " + cmd;
                        terminal.error(msg).error(e.stack);
                    }
                });
            } catch (e) {
                //invalid json - ignore
            }
        }
    }
    exec_hash();
    save = true;
});

It use jQuery hashchange and I've added onAfterCommand callback (I dind't push onAfterCommand into devel branch yet). I not sure if I should put that code into terminal. Maybe I should just put it as example.

@jcubic

This comment has been minimized.

Show comment
Hide comment
@jcubic

jcubic May 30, 2014

Owner

Here is demo http://terminal.jcubic.pl/latest.html (it use uncommited jquery.terminal-src.js file).

Owner

jcubic commented May 30, 2014

Here is demo http://terminal.jcubic.pl/latest.html (it use uncommited jquery.terminal-src.js file).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment