File size: 7,032 Bytes
01cd082
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
define([
    'base/js/namespace',
    'base/js/events',
    'notebook/js/codecell',
    'notebook/js/textcell',
    'jquery'
], function (
    Jupyter,
    events,
    codecell,
    textcell,
    $
) {
    'use strict';

    var CodeCell = codecell.CodeCell;
    var MarkdownCell = textcell.MarkdownCell;

    var mod_name = 'Freeze';
    var log_prefix = '[' + mod_name + ']';

    // defaults, overridden by server's config
    var options = {
        readonly_color: '#fffef0',
        frozen_color: '#f0feff'
    };

    function patch_MarkdownCell_unrender () {
        console.log('[Freeze] patching MarkdownCell.prototype.unrender');
        var old_unrender = MarkdownCell.prototype.unrender;

        MarkdownCell.prototype.unrender = function () {
            // console.log('[Freeze] patched unrender applied');
            if (this.metadata.run_control === undefined ||
                !this.metadata.run_control.frozen
            ) {
                old_unrender.apply(this, arguments);
            }
        };
    }

    function patch_CodeCell_execute () {
        console.log('[Freeze] patching CodeCell.prototype.execute');
        var old_execute = CodeCell.prototype.execute;

        CodeCell.prototype.execute = function () {
            if (this.metadata.run_control === undefined ||
                !this.metadata.run_control.frozen
            ) {
                old_execute.apply(this, arguments);
            }
        };
    }

    // Migrate old metadata format to new notebook-defined metadata.editable
    function migrate_state (cell) {
        if (cell.metadata.run_control !== undefined) {
            if (cell instanceof CodeCell || cell instanceof MarkdownCell) {
                if (cell.metadata.run_control.read_only === true) {
                    cell.metadata.editable = false;
                }
            }
            else {
                // remove metadata irrelevant to non-code/markdown cells
                delete cell.metadata.run_control.frozen;
            }
            // remove old key replaced by metadata.editable
            delete cell.metadata.run_control.read_only;
            // remove whole object if it's now empty
            if (Object.keys(cell.metadata.run_control).length === 0) {
                delete cell.metadata.run_control;
            }
        }
    }

    function get_state (cell) {
        if (cell.metadata.editable === false && (cell instanceof CodeCell || cell instanceof MarkdownCell)) {
            if (cell.metadata.run_control !== undefined && cell.metadata.run_control.frozen) {
                return 'frozen';
            }
            return 'readonly';
        }
        return 'normal';
    }

    function set_state(cell, state) {
        if (!(cell instanceof CodeCell || cell instanceof MarkdownCell)) {
            return;
        }

        state = state || 'normal';
        var bg;
        switch (state) {
            case 'normal':
                cell.metadata.editable = true;
                cell.metadata.deletable = true;
                if (cell.metadata.run_control !== undefined) {
                    delete cell.metadata.run_control.frozen;
                }
                bg = "";
                break;
            case 'read_only':
            case 'readonly':
                cell.metadata.editable = false;
                cell.metadata.deletable = false;
                if (cell.metadata.run_control !== undefined) {
                    delete cell.metadata.run_control.frozen;
                }
                bg = options.readonly_color;
                break;
            case 'frozen':
                cell.metadata.editable = false;
                cell.metadata.deletable = false;
                $.extend(true, cell.metadata, {run_control: {frozen: true}});
                bg = options.frozen_color;
                break;
        }
        // remove whole object if it's now empty
        if (cell.metadata.run_control !== undefined && Object.keys(cell.metadata.run_control).length === 0) {
            delete cell.metadata.run_control;
        }
        cell.code_mirror.setOption('readOnly', !cell.metadata.editable);
        var prompt = cell.element.find('div.input_area');
        prompt.css("background-color", bg);
    }

    function set_state_selected (state) {
        var cells = Jupyter.notebook.get_selected_cells();
        for (var i = 0; i < cells.length; i++) {
            set_state(cells[i], state);
        }
    }

    function button_callback(state) {
        set_state_selected(state);
        var dirty_state = {value: true};
        events.trigger("set_dirty.Notebook", dirty_state);
    }

    function make_normal_selected () {
        button_callback('normal');
    }

    function make_read_only_selected () {
        button_callback('read_only');
    }

    function make_frozen_selected () {
        button_callback('frozen');
    }

    function initialize_states () {
        var cells = Jupyter.notebook.get_cells();
        for (var i = 0; i < cells.length; i++) {
            var cell = cells[i];
            migrate_state(cell);
            var state = get_state(cell);
            set_state(cell, state);
        }
    }

    function load_extension () {
        Jupyter.toolbar.add_buttons_group([
            Jupyter.keyboard_manager.actions.register ({
                help : 'lift restrictions from selected cells',
                icon : 'fa-unlock-alt',
                handler : make_normal_selected
            }, 'make-cells-normal', mod_name),
            Jupyter.keyboard_manager.actions.register({
                help : 'make selected cells read-only',
                icon: 'fa-lock',
                handler : make_read_only_selected
            }, 'make-cells-read-only', mod_name),
            Jupyter.keyboard_manager.actions.register({
                help : 'freeze selected cells',
                icon : 'fa-asterisk',
                handler : make_frozen_selected
            }, 'freeze-cells', mod_name)
        ]);

        patch_CodeCell_execute();
        patch_MarkdownCell_unrender();

        Jupyter.notebook.config.loaded.then(function on_config_loaded () {
            $.extend(true, options, Jupyter.notebook.config.data[mod_name]);
        }, function on_config_load_error (reason) {
            console.warn(log_prefix, 'Using defaults after error loading config:', reason);
        }).then(function do_stuff_with_config () {
            events.on("notebook_loaded.Notebook", initialize_states);
            if (Jupyter.notebook !== undefined && Jupyter.notebook._fully_loaded) {
                // notebook already loaded, so we missed the event, so update all
                initialize_states();
            }
        }).catch(function on_error (reason) {
            console.error(log_prefix, 'Error:', reason);
        });
    }

    return {
        get_state : get_state,
        set_state : set_state,
        load_jupyter_extension : load_extension,
        load_ipython_extension : load_extension
    };
});