<commit_before>const Lang = imports.lang;
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const Gdk = imports.gi.Gdk;
const Gtk = imports.gi.Gtk;
const GtkSource = imports.gi.GtkSource;
const Pango = imports.gi.Pango;
const Utils = imports.Utils;

const TextView = new Lang.Class({
  Name: 'TextView',
  Extends: GtkSource.View,
  Signals: {
    'offset-changed': { param_types: [ GObject.TYPE_ULONG ] },
    'selection-changed': { param_types: [ GObject.TYPE_ULONG, GObject.TYPE_ULONG ] },
  },

  _colors: {
    'highlight': { name: 'highlight',
                   background: 'lightblue' },
    'highlight2': { name: 'highlight2',
                   background: 'lightgreen' },
    'alt-highlight': { name: 'alt-highlight',
                       background: '#fde5f6' },
    'hint': { name: 'hint',
              weight: Pango.Weight.BOLD },
    'error': { name: 'error',
               background: 'red' },
  },

  _init: function(args) {
    this.parent(Utils.copyObjectBut(args, 'language'));
    this.visible = true;
    this.show_line_numbers = true;
    this.following_highlight = false;
    this.monospace = true;
    this._data = '';
    this._lastHighlight = 'highlight';

    if (args)
      this.language = args.language;

    this._tagsOffsets = {}
    let tag_table = this.buffer.get_tag_table();
    for (let i in this._colors) {
      let tag = new Gtk.TextTag(this._colors[i]);
      tag_table.add(tag);
      this._tagsOffsets[i] = { start: 0, end: 0 };
    }

    this._setInControl(false);

    this.connect('key-press-event', this._keyPressed.bind(this));
    this.connect('key-release-event', this._keyReleased.bind(this));
    this.connect('button-release-event', this._buttonReleased.bind(this));
    this.connect('motion-notify-event', this._motionNotified.bind(this));
    this.connect('size-allocate', this._sizeAllocated.bind(this));
  },

  get language() {
    let lang = this.buffer.get_language();
    if (!lang) return '';
    lang.get_id();
  },

  set language(v) {
    if (!v) return;
    let lang_manager = GtkSource.LanguageManager.get_default();
    this.buffer.set_language(lang_manager.get_language(v));
  },

  _setInControl: function(value) {
    this._inControl = value;
  },
  _getInControl: function() {
    return this._inControl;
  },

  _buttonReleased: function(widget, event) {
    this._emitSignalOnSelection('selection-changed');
    return false;
  },
  _keyPressed: function(widget, event) {
    let keyval = event.get_keyval()[1];
    switch (keyval) {
    case Gdk.KEY_Control_L: this._setInControl(true); break;
    }
    return false;
  },
  _keyReleased: function(widget, event) {
    let keyval = event.get_keyval()[1];
    switch (keyval) {
    case Gdk.KEY_Control_L: this._setInControl(false); break;
    }
    return false;
  },
  _motionNotified: function(widget, event) {
    if (!this._getInControl()) return false;
    let [, x, y] = event.get_coords(),
        offset = this.getOffsetAtLocation(x, y);
    this.emit('offset-changed', offset);
    return false;
  },

  _sizeAllocated: function(widget, alloc) {
    this._scrollToHighlight();
    return false;
  },

  _scrollToHighlight: function() {
    let highlight = this._getRange(this._lastHighlight);
    if (!this.following_highlight || highlight.start == highlight.end)
      return;
    let rect1 = this.get_iter_location(this._iterAtOffset(highlight.start)),
        rect2 = this.get_iter_location(this._iterAtOffset(highlight.end));
    let visible = [this.vadjustment.value, this.vadjustment.value + this.vadjustment.page_size];
    if (!Utils.includes(rect1.y, visible[0], visible[1]) ||
        !Utils.includes(rect1.y + rect1.height, visible[0], visible[1]) ||
        !Utils.includes(rect2.y, visible[0], visible[1]) ||
        !Utils.includes(rect2.y + rect2.height, visible[0], visible[1]))
      this.vadjustment.value = (rect1.y + rect2.y) / 2 - this.vadjustment.page_increment / 2;
  },

  _emitSignalOnSelection: function(signal) {
    let [, start_iter, end_iter] = this.buffer.get_selection_bounds();
    let startOffset = start_iter.get_offset(),
        endOffset = end_iter.get_offset();
    if (startOffset != endOffset)
      this.emit(signal, startOffset, endOffset);
  },

  getOffsetAtLocation: function(x, y) {
    x += this.get_hadjustment().get_value();
    y += this.get_vadjustment().get_value();
    let iter = this.get_iter_at_location(x, y);

    return iter.get_offset();
  },
  getRectForRange: function(start, stop) {
    let start_iter = this.buffer.get_iter_at_offset(start);
    let end_iter = this.buffer.get_iter_at_offset(stop);

    let rect1 = this.get_iter_location(start_iter),
        rect2 = this.get_iter_location(end_iter);
    rect1 = rect1.union(rect2);
    // GtkSourceView WTF?
    rect1.x += this.buffer_to_window_coords(Gtk.TextWindowType.WIDGET, 0, 0)[0] - 4;
    rect1.x -= this.hadjustment.value;
    rect1.y -= this.vadjustment.value;
    return rect1;
  },

  _getRange: function(name) {
    return this._tagsOffsets[name];
  },
  _iterAtOffset: function(offset) {
    return this.buffer.get_iter_at_offset(offset);
  },
  _highlightRange: function(name, start, end) {
    this._tagsOffsets[name] = { start: start, end: end };
    this.buffer.apply_tag_by_name(name,
                                  this._iterAtOffset(start),
                                  this._iterAtOffset(end));
  },
  _unhighlightRange: function(name, start, end) {
    this.buffer.remove_tag_by_name(name,
                                   this._iterAtOffset(start),
                                   this._iterAtOffset(end));
  },
  hightlightRange: function(name, start, end) {
    let highlight = this._getRange(name);
    this._unhighlightRange(name, highlight.start, highlight.end);
    this._highlightRange(name, start, end);
    this._lastHighlight = name;
    this._scrollToHighlight();
  },
  removeHighlightRange: function(name) {
    let highlight = this._getRange(name);
    this._unhighlightRange(name, highlight.start, highlight.end);
    highlight.start = highlight.end = 0;
  },
  removeAllHighlight: function() {
    for (let i in this._colors)
      this.removeHighlightRange(i);
  },
  removeSelection: function() {
    let iter = this.buffer.get_iter_at_offset(0);
    this.buffer.select_range(iter, iter);
  },

  getData: function() {
    return this.buffer.get_text(this.buffer.get_start_iter(),
                                this.buffer.get_end_iter(),
                                true);
  },
  setData: function(data) {
    if (!(typeof data === 'string'))
      throw new Error('Cannot display an object other than a string');
    this._data = data;
    this.removeAllHighlight();
    this.buffer.set_text(data, -1);
  },
});
<commit_msg>ui: textview: fix highlight removal problem on user input<commit_after>const Lang = imports.lang;
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const Gdk = imports.gi.Gdk;
const Gtk = imports.gi.Gtk;
const GtkSource = imports.gi.GtkSource;
const Pango = imports.gi.Pango;
const Utils = imports.Utils;

const TextView = new Lang.Class({
  Name: 'TextView',
  Extends: GtkSource.View,
  Signals: {
    'offset-changed': { param_types: [ GObject.TYPE_ULONG ] },
    'selection-changed': { param_types: [ GObject.TYPE_ULONG, GObject.TYPE_ULONG ] },
  },

  _colors: {
    'highlight': { name: 'highlight',
                   background: 'lightblue' },
    'highlight2': { name: 'highlight2',
                   background: 'lightgreen' },
    'alt-highlight': { name: 'alt-highlight',
                       background: '#fde5f6' },
    'hint': { name: 'hint',
              weight: Pango.Weight.BOLD },
    'error': { name: 'error',
               background: 'red' },
  },

  _init: function(args) {
    this.parent(Utils.copyObjectBut(args, 'language'));
    this.visible = true;
    this.show_line_numbers = true;
    this.following_highlight = false;
    this.monospace = true;
    this._data = '';
    this._lastHighlight = 'highlight';

    if (args)
      this.language = args.language;

    this._tagsOffsets = {}
    let tag_table = this.buffer.get_tag_table();
    for (let i in this._colors) {
      let tag = new Gtk.TextTag(this._colors[i]);
      tag_table.add(tag);
      this._tagsOffsets[i] = { start: 0, end: 0 };
    }

    this._setInControl(false);

    this.connect('key-press-event', this._keyPressed.bind(this));
    this.connect('key-release-event', this._keyReleased.bind(this));
    this.connect('button-release-event', this._buttonReleased.bind(this));
    this.connect('motion-notify-event', this._motionNotified.bind(this));
    this.connect('size-allocate', this._sizeAllocated.bind(this));
  },

  get language() {
    let lang = this.buffer.get_language();
    if (!lang) return '';
    lang.get_id();
  },

  set language(v) {
    if (!v) return;
    let lang_manager = GtkSource.LanguageManager.get_default();
    this.buffer.set_language(lang_manager.get_language(v));
  },

  _setInControl: function(value) {
    this._inControl = value;
  },
  _getInControl: function() {
    return this._inControl;
  },

  _buttonReleased: function(widget, event) {
    this._emitSignalOnSelection('selection-changed');
    return false;
  },
  _keyPressed: function(widget, event) {
    let keyval = event.get_keyval()[1];
    switch (keyval) {
    case Gdk.KEY_Control_L: this._setInControl(true); break;
    }
    return false;
  },
  _keyReleased: function(widget, event) {
    let keyval = event.get_keyval()[1];
    switch (keyval) {
    case Gdk.KEY_Control_L: this._setInControl(false); break;
    }
    return false;
  },
  _motionNotified: function(widget, event) {
    if (!this._getInControl()) return false;
    let [, x, y] = event.get_coords(),
        offset = this.getOffsetAtLocation(x, y);
    this.emit('offset-changed', offset);
    return false;
  },

  _sizeAllocated: function(widget, alloc) {
    this._scrollToHighlight();
    return false;
  },

  _scrollToHighlight: function() {
    let highlight = this._getRange(this._lastHighlight);
    if (!this.following_highlight || highlight.start == highlight.end)
      return;
    let rect1 = this.get_iter_location(this._iterAtOffset(highlight.start)),
        rect2 = this.get_iter_location(this._iterAtOffset(highlight.end));
    let visible = [this.vadjustment.value, this.vadjustment.value + this.vadjustment.page_size];
    if (!Utils.includes(rect1.y, visible[0], visible[1]) ||
        !Utils.includes(rect1.y + rect1.height, visible[0], visible[1]) ||
        !Utils.includes(rect2.y, visible[0], visible[1]) ||
        !Utils.includes(rect2.y + rect2.height, visible[0], visible[1]))
      this.vadjustment.value = (rect1.y + rect2.y) / 2 - this.vadjustment.page_increment / 2;
  },

  _emitSignalOnSelection: function(signal) {
    let [, start_iter, end_iter] = this.buffer.get_selection_bounds();
    let startOffset = start_iter.get_offset(),
        endOffset = end_iter.get_offset();
    if (startOffset != endOffset)
      this.emit(signal, startOffset, endOffset);
  },

  getOffsetAtLocation: function(x, y) {
    x += this.get_hadjustment().get_value();
    y += this.get_vadjustment().get_value();
    let iter = this.get_iter_at_location(x, y);

    return iter.get_offset();
  },
  getRectForRange: function(start, stop) {
    let start_iter = this.buffer.get_iter_at_offset(start);
    let end_iter = this.buffer.get_iter_at_offset(stop);

    let rect1 = this.get_iter_location(start_iter),
        rect2 = this.get_iter_location(end_iter);
    rect1 = rect1.union(rect2);
    // GtkSourceView WTF?
    rect1.x += this.buffer_to_window_coords(Gtk.TextWindowType.WIDGET, 0, 0)[0] - 4;
    rect1.x -= this.hadjustment.value;
    rect1.y -= this.vadjustment.value;
    return rect1;
  },

  _getRange: function(name) {
    return this._tagsOffsets[name];
  },
  _iterAtOffset: function(offset) {
    return this.buffer.get_iter_at_offset(offset);
  },
  _highlightRange: function(name, start, end) {
    this._tagsOffsets[name] = { start: start, end: end };
    this.buffer.apply_tag_by_name(name,
                                  this._iterAtOffset(start),
                                  this._iterAtOffset(end));
  },
  _unhighlightRange: function(name, start, end) {
    this.buffer.remove_tag_by_name(name,
                                   this._iterAtOffset(start),
                                   this._iterAtOffset(end));
  },
  hightlightRange: function(name, start, end) {
    let highlight = this._getRange(name);
    this._unhighlightRange(name, highlight.start, highlight.end);
    this._highlightRange(name, start, end);
    this._lastHighlight = name;
    this._scrollToHighlight();
  },
  removeHighlightRange: function(name) {
    let highlight = this._getRange(name);
    this._unhighlightRange(name, highlight.start, highlight.end);
    highlight.start = highlight.end = 0;
  },
  removeAllHighlight: function() {
    for (let i in this._colors) {
      this.buffer.remove_tag_by_name(i,
                                     this.buffer.get_start_iter(),
                                     this.buffer.get_end_iter());
      this._tagsOffsets[i] = { start: 0, end: 0};
    }
  },
  removeSelection: function() {
    let iter = this.buffer.get_iter_at_offset(0);
    this.buffer.select_range(iter, iter);
  },

  getData: function() {
    return this.buffer.get_text(this.buffer.get_start_iter(),
                                this.buffer.get_end_iter(),
                                true);
  },
  setData: function(data) {
    if (!(typeof data === 'string'))
      throw new Error('Cannot display an object other than a string');
    this._data = data;
    this.removeAllHighlight();
    this.buffer.set_text(data, -1);
  },
});
<|endoftext|>


<commit_before>'use strict'

const ipcRenderer = require('electron').ipcRenderer
const remote = require('electron').remote

// Helper function to resolve relative url.
var a = window.top.document.createElement('a')
var resolveURL = function (url) {
  a.href = url
  return a.href
}

// Window object returned by "window.open".
var BrowserWindowProxy = (function () {
  BrowserWindowProxy.proxies = {}

  BrowserWindowProxy.getOrCreate = function (guestId) {
    var base = this.proxies
    base[guestId] != null ? base[guestId] : base[guestId] = new BrowserWindowProxy(guestId)
    return base[guestId]
  }

  BrowserWindowProxy.remove = function (guestId) {
    return delete this.proxies[guestId]
  }

  function BrowserWindowProxy (guestId1) {
    Object.defineProperty(this, 'guestId', {
      configurable: false,
      enumerable: true,
      writeable: false,
      value: guestId1
    })

    this.closed = false
    ipcRenderer.once('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + this.guestId, () => {
      BrowserWindowProxy.remove(this.guestId)
      this.closed = true
    })
  }

  BrowserWindowProxy.prototype.close = function () {
    return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', this.guestId)
  }

  BrowserWindowProxy.prototype.focus = function () {
    return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'focus')
  }

  BrowserWindowProxy.prototype.blur = function () {
    return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'blur')
  }

  BrowserWindowProxy.prototype.print = function () {
    return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'print')
  }

  Object.defineProperty(BrowserWindowProxy.prototype, 'location', {
    get: function () {
      return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'getURL')
    },
    set: function (url) {
      return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'loadURL', url)
    }
  })

  BrowserWindowProxy.prototype.postMessage = function (message, targetOrigin) {
    if (targetOrigin == null) {
      targetOrigin = '*'
    }
    return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, targetOrigin, window.location.origin)
  }

  BrowserWindowProxy.prototype['eval'] = function (...args) {
    return ipcRenderer.send.apply(ipcRenderer, ['ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'executeJavaScript'].concat(args))
  }

  return BrowserWindowProxy
})()

if (process.guestInstanceId == null) {
  // Override default window.close.
  window.close = function () {
    return remote.getCurrentWindow().close()
  }
}

// Make the browser window or guest view emit "new-window" event.
window.open = function (url, frameName, features) {
  var feature, guestId, i, j, len, len1, name, options, ref1, ref2, value
  if (frameName == null) {
    frameName = ''
  }
  if (features == null) {
    features = ''
  }
  options = {}

  const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor']
  const webPreferences = ['zoomFactor', 'nodeIntegration', 'preload']
  const disposition = 'new-window'

  // Make sure to get rid of excessive whitespace in the property name
  ref1 = features.split(/,\s*/)
  for (i = 0, len = ref1.length; i < len; i++) {
    feature = ref1[i]
    ref2 = feature.split(/\s*=/)
    name = ref2[0]
    value = ref2[1]
    value = value === 'yes' || value === '1' ? true : value === 'no' || value === '0' ? false : value
    if (webPreferences.includes(name)) {
      if (options.webPreferences == null) {
        options.webPreferences = {}
      }
      options.webPreferences[name] = value
    } else {
      options[name] = value
    }
  }
  if (options.left) {
    if (options.x == null) {
      options.x = options.left
    }
  }
  if (options.top) {
    if (options.y == null) {
      options.y = options.top
    }
  }
  if (options.title == null) {
    options.title = frameName
  }
  if (options.width == null) {
    options.width = 800
  }
  if (options.height == null) {
    options.height = 600
  }

  // Resolve relative urls.
  url = resolveURL(url)
  for (j = 0, len1 = ints.length; j < len1; j++) {
    name = ints[j]
    if (options[name] != null) {
      options[name] = parseInt(options[name], 10)
    }
  }
  guestId = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, disposition, options)
  if (guestId) {
    return BrowserWindowProxy.getOrCreate(guestId)
  } else {
    return null
  }
}

// Use the dialog API to implement alert().
window.alert = function (message = '', title = '') {
  remote.dialog.showMessageBox(remote.getCurrentWindow(), {
    message: String(message),
    title: String(title),
    buttons: ['OK']
  })
}

// And the confirm().
window.confirm = function (message, title) {
  var buttons, cancelId
  if (title == null) {
    title = ''
  }
  buttons = ['OK', 'Cancel']
  cancelId = 1
  return !remote.dialog.showMessageBox(remote.getCurrentWindow(), {
    message: message,
    title: title,
    buttons: buttons,
    cancelId: cancelId
  })
}

// But we do not support prompt().
window.prompt = function () {
  throw new Error('prompt() is and will not be supported.')
}

if (process.openerId != null) {
  window.opener = BrowserWindowProxy.getOrCreate(process.openerId)
}

ipcRenderer.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (event, sourceId, message, sourceOrigin) {
  // Manually dispatch event instead of using postMessage because we also need to
  // set event.source.
  event = document.createEvent('Event')
  event.initEvent('message', false, false)
  event.data = message
  event.origin = sourceOrigin
  event.source = BrowserWindowProxy.getOrCreate(sourceId)
  window.dispatchEvent(event)
})

// Forward history operations to browser.
var sendHistoryOperation = function (...args) {
  return ipcRenderer.send.apply(ipcRenderer, ['ELECTRON_NAVIGATION_CONTROLLER'].concat(args))
}

var getHistoryOperation = function (...args) {
  return ipcRenderer.sendSync.apply(ipcRenderer, ['ELECTRON_SYNC_NAVIGATION_CONTROLLER'].concat(args))
}

window.history.back = function () {
  return sendHistoryOperation('goBack')
}

window.history.forward = function () {
  return sendHistoryOperation('goForward')
}

window.history.go = function (offset) {
  return sendHistoryOperation('goToOffset', offset)
}

Object.defineProperty(window.history, 'length', {
  get: function () {
    return getHistoryOperation('length')
  }
})

// The initial visibilityState.
let cachedVisibilityState = process.argv.includes('--hidden-page') ? 'hidden' : 'visible'

// Subscribe to visibilityState changes.
ipcRenderer.on('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', function (event, visibilityState) {
  if (cachedVisibilityState !== visibilityState) {
    cachedVisibilityState = visibilityState
    document.dispatchEvent(new Event('visibilitychange'))
  }
})

// Make document.hidden and document.visibilityState return the correct value.
Object.defineProperty(document, 'hidden', {
  get: function () {
    return cachedVisibilityState !== 'visible'
  }
})

Object.defineProperty(document, 'visibilityState', {
  get: function () {
    return cachedVisibilityState
  }
})
<commit_msg>Apply resolveURL when set a location of a window opened by window.open()<commit_after>'use strict'

const ipcRenderer = require('electron').ipcRenderer
const remote = require('electron').remote

// Helper function to resolve relative url.
var a = window.top.document.createElement('a')
var resolveURL = function (url) {
  a.href = url
  return a.href
}

// Window object returned by "window.open".
var BrowserWindowProxy = (function () {
  BrowserWindowProxy.proxies = {}

  BrowserWindowProxy.getOrCreate = function (guestId) {
    var base = this.proxies
    base[guestId] != null ? base[guestId] : base[guestId] = new BrowserWindowProxy(guestId)
    return base[guestId]
  }

  BrowserWindowProxy.remove = function (guestId) {
    return delete this.proxies[guestId]
  }

  function BrowserWindowProxy (guestId1) {
    Object.defineProperty(this, 'guestId', {
      configurable: false,
      enumerable: true,
      writeable: false,
      value: guestId1
    })

    this.closed = false
    ipcRenderer.once('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + this.guestId, () => {
      BrowserWindowProxy.remove(this.guestId)
      this.closed = true
    })
  }

  BrowserWindowProxy.prototype.close = function () {
    return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', this.guestId)
  }

  BrowserWindowProxy.prototype.focus = function () {
    return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'focus')
  }

  BrowserWindowProxy.prototype.blur = function () {
    return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'blur')
  }

  BrowserWindowProxy.prototype.print = function () {
    return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'print')
  }

  Object.defineProperty(BrowserWindowProxy.prototype, 'location', {
    get: function () {
      return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'getURL')
    },
    set: function (url) {
      url = resolveURL(url)
      return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'loadURL', url)
    }
  })

  BrowserWindowProxy.prototype.postMessage = function (message, targetOrigin) {
    if (targetOrigin == null) {
      targetOrigin = '*'
    }
    return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, targetOrigin, window.location.origin)
  }

  BrowserWindowProxy.prototype['eval'] = function (...args) {
    return ipcRenderer.send.apply(ipcRenderer, ['ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'executeJavaScript'].concat(args))
  }

  return BrowserWindowProxy
})()

if (process.guestInstanceId == null) {
  // Override default window.close.
  window.close = function () {
    return remote.getCurrentWindow().close()
  }
}

// Make the browser window or guest view emit "new-window" event.
window.open = function (url, frameName, features) {
  var feature, guestId, i, j, len, len1, name, options, ref1, ref2, value
  if (frameName == null) {
    frameName = ''
  }
  if (features == null) {
    features = ''
  }
  options = {}

  const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor']
  const webPreferences = ['zoomFactor', 'nodeIntegration', 'preload']
  const disposition = 'new-window'

  // Make sure to get rid of excessive whitespace in the property name
  ref1 = features.split(/,\s*/)
  for (i = 0, len = ref1.length; i < len; i++) {
    feature = ref1[i]
    ref2 = feature.split(/\s*=/)
    name = ref2[0]
    value = ref2[1]
    value = value === 'yes' || value === '1' ? true : value === 'no' || value === '0' ? false : value
    if (webPreferences.includes(name)) {
      if (options.webPreferences == null) {
        options.webPreferences = {}
      }
      options.webPreferences[name] = value
    } else {
      options[name] = value
    }
  }
  if (options.left) {
    if (options.x == null) {
      options.x = options.left
    }
  }
  if (options.top) {
    if (options.y == null) {
      options.y = options.top
    }
  }
  if (options.title == null) {
    options.title = frameName
  }
  if (options.width == null) {
    options.width = 800
  }
  if (options.height == null) {
    options.height = 600
  }

  // Resolve relative urls.
  url = resolveURL(url)
  for (j = 0, len1 = ints.length; j < len1; j++) {
    name = ints[j]
    if (options[name] != null) {
      options[name] = parseInt(options[name], 10)
    }
  }
  guestId = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, disposition, options)
  if (guestId) {
    return BrowserWindowProxy.getOrCreate(guestId)
  } else {
    return null
  }
}

// Use the dialog API to implement alert().
window.alert = function (message = '', title = '') {
  remote.dialog.showMessageBox(remote.getCurrentWindow(), {
    message: String(message),
    title: String(title),
    buttons: ['OK']
  })
}

// And the confirm().
window.confirm = function (message, title) {
  var buttons, cancelId
  if (title == null) {
    title = ''
  }
  buttons = ['OK', 'Cancel']
  cancelId = 1
  return !remote.dialog.showMessageBox(remote.getCurrentWindow(), {
    message: message,
    title: title,
    buttons: buttons,
    cancelId: cancelId
  })
}

// But we do not support prompt().
window.prompt = function () {
  throw new Error('prompt() is and will not be supported.')
}

if (process.openerId != null) {
  window.opener = BrowserWindowProxy.getOrCreate(process.openerId)
}

ipcRenderer.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (event, sourceId, message, sourceOrigin) {
  // Manually dispatch event instead of using postMessage because we also need to
  // set event.source.
  event = document.createEvent('Event')
  event.initEvent('message', false, false)
  event.data = message
  event.origin = sourceOrigin
  event.source = BrowserWindowProxy.getOrCreate(sourceId)
  window.dispatchEvent(event)
})

// Forward history operations to browser.
var sendHistoryOperation = function (...args) {
  return ipcRenderer.send.apply(ipcRenderer, ['ELECTRON_NAVIGATION_CONTROLLER'].concat(args))
}

var getHistoryOperation = function (...args) {
  return ipcRenderer.sendSync.apply(ipcRenderer, ['ELECTRON_SYNC_NAVIGATION_CONTROLLER'].concat(args))
}

window.history.back = function () {
  return sendHistoryOperation('goBack')
}

window.history.forward = function () {
  return sendHistoryOperation('goForward')
}

window.history.go = function (offset) {
  return sendHistoryOperation('goToOffset', offset)
}

Object.defineProperty(window.history, 'length', {
  get: function () {
    return getHistoryOperation('length')
  }
})

// The initial visibilityState.
let cachedVisibilityState = process.argv.includes('--hidden-page') ? 'hidden' : 'visible'

// Subscribe to visibilityState changes.
ipcRenderer.on('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', function (event, visibilityState) {
  if (cachedVisibilityState !== visibilityState) {
    cachedVisibilityState = visibilityState
    document.dispatchEvent(new Event('visibilitychange'))
  }
})

// Make document.hidden and document.visibilityState return the correct value.
Object.defineProperty(document, 'hidden', {
  get: function () {
    return cachedVisibilityState !== 'visible'
  }
})

Object.defineProperty(document, 'visibilityState', {
  get: function () {
    return cachedVisibilityState
  }
})
<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter,
fs = require('fs'),
_path = require('path');

module.exports = walkdir;

walkdir.find = walkdir.walk = walkdir;

walkdir.sync = function(path,options,cb){
  if(typeof options == 'function') cb = options;
  options = options || {};
  options.sync = true;
  return walkdir(path,options,cb);

};

function walkdir(path,options,cb){

  if(typeof options == 'function') cb = options;

  options = options || {};

  var emitter = new EventEmitter(),
  allPaths = (options.return_object?{}:[]),
  resolved = false,
  inos = {},
  stop = 0,
  pause = null,
  ended = 0,
  jobs=0,
  job = function(value) {
    jobs += value;
    if(value < 1 && !tick) {
      tick = 1;
      process.nextTick(function(){
        tick = 0;
        if(jobs <= 0 && !ended) {
          ended = 1;
          emitter.emit('end');
        }
      });
    }
  }, tick = 0;

  //mapping is stat functions to event names.
  var statIs = [['isFile','file'],['isDirectory','directory'],['isSymbolicLink','link'],['isSocket','socket'],['isFIFO','fifo'],['isBlockDevice','blockdevice'],['isCharacterDevice','characterdevice']];

  var statter = function (path,first,depth) {
    job(1);
    var statAction = function fn(err,stat,data) {

      job(-1);
      if(stop) return;

      // in sync mode i found that node will sometimes return a null stat and no error =(
      // this is reproduceable in file descriptors that no longer exist from this process
      // after a readdir on /proc/3321/task/3321/ for example. Where 3321 is this pid
      // node @ v0.6.10
      if(err || !stat) {
        emitter.emit('fail',path,err);
        return;
      }

      //if i have evented this inode already dont again.
      if(inos[stat.dev+'-'+stat.ino] && stat.ino) return;
      inos[stat.dev+'-'+stat.ino] = 1;

      if (first && stat.isDirectory()) {
        emitter.emit('targetdirectory',path,stat,depth);
        return;
      }

      emitter.emit('path', path, stat,depth);

      var i,name;

      for(var j=0,k=statIs.length;j<k;j++) {
        if(stat[statIs[j][0]]()) {
          emitter.emit(statIs[j][1],path,stat,depth);
          break;
        }
      }
    };

    if(options.sync) {
      var stat,ex;
      try{
        stat = fs.lstatSync(path);
      } catch (e) {
        ex = e;
      }

      statAction(ex,stat);
    } else {
        fs.lstat(path,statAction);
    }
  },readdir = function(path,stat,depth){
    if(!resolved) {
      path = _path.resolve(path);
      resolved = 1;
    }

    if(options.max_depth && depth >= options.max_depth){
      emitter.emit('maxdepth',path,stat,depth);
      return;
    }

    job(1);
    var readdirAction = function(err,files) {
      job(-1);
      if (err || !files) {
        //permissions error or invalid files
        emitter.emit('fail',path,err);
        return;
      }

      if(!files.length) {
        // empty directory event.
        emitter.emit('empty',path,stat,depth);
        return;
      }

      if(path == '/') path='';
      for(var i=0,j=files.length;i<j;i++){
        statter(path+'/'+files[i],false,(depth||0)+1);
      }

    };

    //use same pattern for sync as async api
    if(options.sync) {
      var e,files;
      try {
          files = fs.readdirSync(path);
      } catch (e) { }

      readdirAction(e,files);
    } else {
      fs.readdir(path,readdirAction);
    }
  };

  if (options.follow_symlinks) {
    var linkAction = function(err,path,depth){
      job(-1);
      //TODO should fail event here on error?
      statter(path,false,depth);
    };

    emitter.on('link',function(path,stat,depth){
      job(1);
      if(options.sync) {
        var lpath,ex;
        try {
          lpath = fs.readlinkSync(path);
        } catch(e) {
          ex = e;
        }
        linkAction(ex,_path.resolve(_path.dirname(path),lpath),depth);

      } else {
        fs.readlink(path,function(err,lpath){
          linkAction(err,_path.resolve(_path.dirname(path),lpath),depth);
        });
      }
    });
  }

  if (cb) {
    emitter.on('path',cb);
  }

  if (options.sync) {
    if(!options.no_return){
      emitter.on('path',function(path,stat){
        if(options.return_object) allPaths[path] = stat;
        else allPaths.push(path);
      });
    }
  }

  if (!options.no_recurse) {
    emitter.on('directory',readdir);
  }
  //directory that was specified by argument.
  emitter.once('targetdirectory',readdir);
  //only a fail on the path specified by argument is fatal
  emitter.once('fail',function(_path,err){
    //if the first dir fails its a real error
    if(path == _path) {
      emitter.emit('error',path,err);
    }
  });

  statter(path,1);
  if (options.sync) {
    return allPaths;
  } else {
    //support stopping everything.
    emitter.end = emitter.stop = function(){stop = 1;};
    //support pausing everything
    var emitQ = [];
    emitter.pause = function(){
      job(1);
      pause = true;
      emitter.emit = function(){
        emitQ.push(arguments);
      };
    };
    // support getting the show going again
    emitter.resume = function(){
      if(!pause) return;
      pause = false;
      // not pending
      job(-1);
      //replace emit
      emitter.emit = EventEmitter.prototype.emit;
      // local ref
      var q = emitQ;
      // clear ref to prevent infinite loops
      emitQ = [];
      while(q.length) {
        emitter.emit.apply(emitter,q.shift());
      }
    };

    return emitter;
  }

}
<commit_msg>Use cross-platform node's path.sep<commit_after>var EventEmitter = require('events').EventEmitter,
fs = require('fs'),
_path = require('path');

module.exports = walkdir;

walkdir.find = walkdir.walk = walkdir;

walkdir.sync = function(path,options,cb){
  if(typeof options == 'function') cb = options;
  options = options || {};
  options.sync = true;
  return walkdir(path,options,cb);

};

function walkdir(path,options,cb){

  if(typeof options == 'function') cb = options;

  options = options || {};

  var emitter = new EventEmitter(),
  allPaths = (options.return_object?{}:[]),
  resolved = false,
  inos = {},
  stop = 0,
  pause = null,
  ended = 0,
  jobs=0,
  job = function(value) {
    jobs += value;
    if(value < 1 && !tick) {
      tick = 1;
      process.nextTick(function(){
        tick = 0;
        if(jobs <= 0 && !ended) {
          ended = 1;
          emitter.emit('end');
        }
      });
    }
  }, tick = 0;

  //mapping is stat functions to event names.
  var statIs = [['isFile','file'],['isDirectory','directory'],['isSymbolicLink','link'],['isSocket','socket'],['isFIFO','fifo'],['isBlockDevice','blockdevice'],['isCharacterDevice','characterdevice']];

  var statter = function (path,first,depth) {
    job(1);
    var statAction = function fn(err,stat,data) {

      job(-1);
      if(stop) return;

      // in sync mode i found that node will sometimes return a null stat and no error =(
      // this is reproduceable in file descriptors that no longer exist from this process
      // after a readdir on /proc/3321/task/3321/ for example. Where 3321 is this pid
      // node @ v0.6.10
      if(err || !stat) {
        emitter.emit('fail',path,err);
        return;
      }

      //if i have evented this inode already dont again.
      if(inos[stat.dev+'-'+stat.ino] && stat.ino) return;
      inos[stat.dev+'-'+stat.ino] = 1;

      if (first && stat.isDirectory()) {
        emitter.emit('targetdirectory',path,stat,depth);
        return;
      }

      emitter.emit('path', path, stat,depth);

      var i,name;

      for(var j=0,k=statIs.length;j<k;j++) {
        if(stat[statIs[j][0]]()) {
          emitter.emit(statIs[j][1],path,stat,depth);
          break;
        }
      }
    };

    if(options.sync) {
      var stat,ex;
      try{
        stat = fs.lstatSync(path);
      } catch (e) {
        ex = e;
      }

      statAction(ex,stat);
    } else {
        fs.lstat(path,statAction);
    }
  },readdir = function(path,stat,depth){
    if(!resolved) {
      path = _path.resolve(path);
      resolved = 1;
    }

    if(options.max_depth && depth >= options.max_depth){
      emitter.emit('maxdepth',path,stat,depth);
      return;
    }

    job(1);
    var readdirAction = function(err,files) {
      job(-1);
      if (err || !files) {
        //permissions error or invalid files
        emitter.emit('fail',path,err);
        return;
      }

      if(!files.length) {
        // empty directory event.
        emitter.emit('empty',path,stat,depth);
        return;
      }

      if(path == _path.sep) path='';
      for(var i=0,j=files.length;i<j;i++){
        statter(path+_path.sep+files[i],false,(depth||0)+1);
      }

    };

    //use same pattern for sync as async api
    if(options.sync) {
      var e,files;
      try {
          files = fs.readdirSync(path);
      } catch (e) { }

      readdirAction(e,files);
    } else {
      fs.readdir(path,readdirAction);
    }
  };

  if (options.follow_symlinks) {
    var linkAction = function(err,path,depth){
      job(-1);
      //TODO should fail event here on error?
      statter(path,false,depth);
    };

    emitter.on('link',function(path,stat,depth){
      job(1);
      if(options.sync) {
        var lpath,ex;
        try {
          lpath = fs.readlinkSync(path);
        } catch(e) {
          ex = e;
        }
        linkAction(ex,_path.resolve(_path.dirname(path),lpath),depth);

      } else {
        fs.readlink(path,function(err,lpath){
          linkAction(err,_path.resolve(_path.dirname(path),lpath),depth);
        });
      }
    });
  }

  if (cb) {
    emitter.on('path',cb);
  }

  if (options.sync) {
    if(!options.no_return){
      emitter.on('path',function(path,stat){
        if(options.return_object) allPaths[path] = stat;
        else allPaths.push(path);
      });
    }
  }

  if (!options.no_recurse) {
    emitter.on('directory',readdir);
  }
  //directory that was specified by argument.
  emitter.once('targetdirectory',readdir);
  //only a fail on the path specified by argument is fatal
  emitter.once('fail',function(_path,err){
    //if the first dir fails its a real error
    if(path == _path) {
      emitter.emit('error',path,err);
    }
  });

  statter(path,1);
  if (options.sync) {
    return allPaths;
  } else {
    //support stopping everything.
    emitter.end = emitter.stop = function(){stop = 1;};
    //support pausing everything
    var emitQ = [];
    emitter.pause = function(){
      job(1);
      pause = true;
      emitter.emit = function(){
        emitQ.push(arguments);
      };
    };
    // support getting the show going again
    emitter.resume = function(){
      if(!pause) return;
      pause = false;
      // not pending
      job(-1);
      //replace emit
      emitter.emit = EventEmitter.prototype.emit;
      // local ref
      var q = emitQ;
      // clear ref to prevent infinite loops
      emitQ = [];
      while(q.length) {
        emitter.emit.apply(emitter,q.shift());
      }
    };

    return emitter;
  }

}
<|endoftext|>


<commit_before>/*
 Shared Namespace Library for JavaScript Code Modules

 Usage:
   Components.utils.import('resource://my-modules/namespace.jsm');
   var namespace = getNamespaceFor('mylibrary');
   namespace.func1 = function() { ... };
   namespace.func2 = function() { ... };
   var EXPORTED_SYMBOLS = ['func1', 'func2'];
   var func1 = namespace.func1;
   var func2 = namespace.func2;

 lisence: The MIT License, Copyright (c) 2010 SHIMODA "Piro" Hiroshi
   http://www.cozmixng.org/repos/piro/fx3-compatibility-lib/trunk/license.txt
 original:
   http://www.cozmixng.org/repos/piro/fx3-compatibility-lib/trunk/namespace.js
*/

var EXPORTED_SYMBOLS = ['getNamespaceFor'];

const Cc = Components.classes;
const Ci = Components.interfaces;

const currentRevision = 1;

const Application = '@mozilla.org/fuel/application;1' in Cc ?
						Cc['@mozilla.org/fuel/application;1'].getService(Ci.fuelIApplication) :
					'@mozilla.org/steel/application;1' in Cc ?
						Cc['@mozilla.org/steel/application;1'].getService(Ci.steelIApplication) :
						null ;
if (!Application)
	throw new Error('there is no backend for shared namespaces!');

const storage = Application.storage;

if (!storage.has('sharednamespaces@piro.sakura.ne.jp'))
	storage.set('sharednamespaces@piro.sakura.ne.jp', {});

var namespaces = storage.get('sharednamespaces@piro.sakura.ne.jp', null);

function getNamespaceFor(aName)
{
	if (!aName)
		throw new Error('you must specify the name of the namespace!');

	if (!(aName in namespaces))
		namespaces[aName] = {};

	return namespaces[aName];
}
<commit_msg>コメント修正<commit_after>/*
 Shared Namespace Library for JavaScript Code Modules

 Usage:
   Components.utils.import('resource://my-modules/namespace.jsm');
   var namespace = getNamespaceFor('mylibrary');
   if (!namespace.func1) {
     namespace.func1 = function() { ... };
     namespace.func2 = function() { ... };
   }
   var EXPORTED_SYMBOLS = ['func1', 'func2'];
   var func1 = namespace.func1;
   var func2 = namespace.func2;

 lisence: The MIT License, Copyright (c) 2010 SHIMODA "Piro" Hiroshi
   http://www.cozmixng.org/repos/piro/fx3-compatibility-lib/trunk/license.txt
 original:
   http://www.cozmixng.org/repos/piro/fx3-compatibility-lib/trunk/namespace.js
*/

var EXPORTED_SYMBOLS = ['getNamespaceFor'];

const Cc = Components.classes;
const Ci = Components.interfaces;

const currentRevision = 1;

const Application = '@mozilla.org/fuel/application;1' in Cc ?
						Cc['@mozilla.org/fuel/application;1'].getService(Ci.fuelIApplication) :
					'@mozilla.org/steel/application;1' in Cc ?
						Cc['@mozilla.org/steel/application;1'].getService(Ci.steelIApplication) :
						null ;
if (!Application)
	throw new Error('there is no backend for shared namespaces!');

const storage = Application.storage;

if (!storage.has('sharednamespaces@piro.sakura.ne.jp'))
	storage.set('sharednamespaces@piro.sakura.ne.jp', {});

var namespaces = storage.get('sharednamespaces@piro.sakura.ne.jp', null);

function getNamespaceFor(aName)
{
	if (!aName)
		throw new Error('you must specify the name of the namespace!');

	if (!(aName in namespaces))
		namespaces[aName] = {};

	return namespaces[aName];
}
<|endoftext|>


<commit_before>/*
 * Nanoblok (Experimental) - Web-Based Graphical Editor for Game Sprite Development
 * http://code.google.com/p/nanoblok/
 * Copyright (c) 2009-2010 Alex Trujillo
 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
 *
 * Summary for block.js:
 * This file holds outdated SVG-based block rendering code. This stores data in the DOM, which uses more resources and is slower.
 * Soon to be deprecated.
 */

// Draws hexagonal points in iso perspective, based on hard coordinate and size of block. Depends on an orientation array (see block-grid.png in Nanoblok Extras). Returns static coordinates of where to draw blocks on the screen.
function hexiso (offset, blockSize) {
	// Builds an array of points that corresponds to an entire isometric block (six points), including center (7). Arrays for both X and Y coordinates. Offset added to hexagon proportions.
	var isoX = Array();
	isoX [1] = blockSize.half + offset.x;
	isoX [2] = blockSize.full + offset.x;
	isoX [3] = blockSize.full + offset.x;
	isoX [4] = blockSize.half + offset.x;
	isoX [5] = offset.x;
	isoX [6] = offset.x;
	isoX [7] = blockSize.half + offset.x;

	var isoY = Array();
	isoY [1] = offset.y;
	isoY [2] = blockSize.quarter + offset.y;
	isoY [3] = blockSize.third + offset.y;
	isoY [4] = blockSize.full + offset.y;
	isoY [5] = blockSize.third + offset.y;
	isoY [6] = blockSize.quarter + offset.y;
	isoY [7] = blockSize.half + offset.y;

	return {x: isoX, y: isoY};
}

// A very useful function to draw a set of coordinates in a hexagonal setup at a certain offset.
// Very necessary for an isometric setup such as this.
function canvasDrawSet (hexSet, offset, settings) {
	var canvas = null;

	if (settings.grid) {
		canvas = 'grids';
	}
	else if ($C.swatchActive) {
		canvas = 'colors';
	}
	else if ($C.selected.tool == "toolButtonSelect") {
		canvas = 'selection';
	}
	else {
		canvas = 'blocks';
	}

	var ctx = context(canvas);

	ctx.globalAlpha = 1.0;

	if (settings.stroke !== false) {
		ctx.strokeStyle = settings.stroke;
	}
	if (settings.fill !== false) {
		ctx.fillStyle = settings.fill;
	}

	var hexSpot = hexSet.pop();
	var coorSet = hexiso(offset, $C.blockSize);
	var offsY = -35;

	ctx.beginPath();
	ctx.moveTo(coorSet.x[hexSpot], coorSet.y[hexSpot] + offsY);

	var i = 0;

	while (i < hexSet.length || i == 7) {
		hexSpot = hexSet.pop();
		ctx.lineTo(coorSet.x[hexSpot], coorSet.y[hexSpot] + offsY);
		i = i++;
	}

	if (settings.closed) {
		ctx.closePath();
	}

	if (settings.stroke !== false) {
		ctx.stroke();
	}
	if (settings.fill !== false) {
		ctx.fill();
	}
}

// colorBlock is used to turn the color index into a color object (with separate color values for each face as well as its lines).
// It basically takes this color index and corresponds it to an RGB value held in the color palette in $C.
// Later a better system for handling color must be designed.
/*function colorBlock (colorID) {
	var color = $C.palette[colorID];
	var colorR = color[0];
	var colorG = color[1];
	var colorB = color[2];
	var colorLeft = "rgb(" + colorR + ", " + colorG + ", " + colorB + ")";
	var colorRight = "rgb(" + (colorR + 20) + ", " + (colorG + 20) + ", " + (colorB + 20) + ")";
	var colorTop = "rgb(" + (colorR + 40) + ", " + (colorG + 40) + ", " + (colorB + 40) + ")";
	var colorLines = "rgb(" + (colorR -20) + ", " + (colorG - 20) + ", " + (colorB - 20) + ")";

	return {left: colorLeft, right: colorRight, top: colorTop, inset: colorLines};
}*/

var cubeShift = {a: -20, b: -10, c: 0, d: -30};
var blokShift = {a: -40, b: -20, c: 0, d: -60};

function colorBlockNew (color) {
	var blockColors = new Object();
	var shift;

	if ($C.swatchActive) {
		shift = cubeShift;
	}
	else {
		shift = blokShift;
	}

	blockColors.left = "rgb(" + (color.r + shift.a) + ", " + (color.g + shift.a) + ", " + (color.b + shift.a) + ")";
	blockColors.right = "rgb(" + (color.r + shift.b) + ", " + (color.g + shift.b) + ", " + (color.b + shift.b) + ")";
	blockColors.top = "rgb(" + (color.r + shift.c) + ", " + (color.g + shift.c) + ", " + (color.b + shift.c) + ")";
	blockColors.inset = "rgb(" + (color.r + shift.d) + ", " + (color.g + shift.d) + ", " + (color.b + shift.d) + ")";

	return blockColors;
}

// Paints a block on the board with proper color and occlusion.
// Takes coors x/y for position, xyz location on the grid, $C, and the color object of the block.
// A color id can be converted into a color object using the colorBlock function.
function canvasBlock (position, location, color) {
	var adjustedPosition = {x: position.x, y: position.y - $C.blockSize.half * (location.z + 1)};

	if ($C.swatchActive) {
		Arr = SwatchGhost;
	}
	// else if ($C.selected.tool == "Select") {
	// 	Arr = SelectionVox;
	// }
	else {
		Arr = Voxel;
	}

	// Top side. Always placed, unless there's a block above it.
	if (Arr[location.x][location.y][location.z + 1] == -1) {
		canvasDrawSet([1, 6, 7, 2], adjustedPosition, {closed: true, fill: color.top, stroke: color.inset});
	}

	// Left side.
	if (Arr[location.x - 1][location.y][location.z] == -1
			&& Arr[location.x - 1][location.y + 1][location.z] == -1) {
		canvasDrawSet([6, 7, 4, 5], adjustedPosition, {closed: true, fill: color.left, stroke: color.inset});
	} else if (Arr[location.x - 1][location.y + 1][location.z] != -1
			&& Arr[location.x - 1][location.y][location.z] == -1) {
		canvasDrawSet([6, 7, 5], adjustedPosition, {closed: true, fill: color.left, stroke: color.inset});
	}

	// Right side.
	if (Arr[location.x][location.y + 1][location.z] == -1
			&& Arr[location.x - 1][location.y + 1][location.z] == -1) {
		canvasDrawSet([2, 7, 4, 3], adjustedPosition, {closed: true, fill: color.right, stroke: color.inset});
	} else if (Arr[location.x - 1][location.y + 1][location.z] != -1
			&& Arr[location.x][location.y + 1][location.z] == -1) {
		canvasDrawSet([2, 7, 3], adjustedPosition, {closed: true, fill: color.right, stroke: color.inset});
	}
}

// Calls canvasBlock to place a block on the grid.
function placeBlock (target) {
	// Make location object.
	var location = {
		x: GridField[target.id].x,
		y: GridField[target.id].y,
		z: $C.layerOffset.z
	}

	// If the same block already exists at the location being painted, don't paint over it again.
	if (Voxel[location.x][location.y][location.z] != $C.selected.color) {
		// Draw the actual block using coordinates using the location of the grid's tiles as a reference for pixel-placement for all the rest of the blocks (this is the first argument). The target.id should look something like "x-123".
		// colorBlock is used to turn the color index into a color object (with separate color values for each face as well as its lines)
		canvasBlock(GridField[target.id].coors, location, colorBlockNew(SwatchField[$C.selected.color][3]));

		// Record information in the Field array, which is for serialization.
		Field.push([location.x, location.y, location.z, $C.selected.color]);
		// As well as the Field index of the block internally using the Voxel array.
		Voxel[location.x][location.y][location.z] = Field.length - 1;

		// Let the user know they've placed a block.
		// loggit("Block placed at " + location.x + ", " + location.y + ", " + location.z + ".")

		// Redraw the display so that this change shows up immediately.
		$C.posInd.redraw();
	}
	else {
		loggit("Same block already exists at location.");
	}
}

function removeBlock (target) {
	var time0 = new Date();

	var location = {
		x: GridField[target.id].x,
		y: GridField[target.id].y,
		z: 0 + $C.layerOffset.z // forgot why I put a zero here.
	}

	if (Voxel[location.x][location.y][location.z] != -1) {
		Voxel[location.x][location.y][location.z] = -1;
		popField(location.x, location.y, location.z);

		$C.posInd.clearBlocks();
		drawAllBlocks();
		// canvasBlockRemove(GridField[target.id].coors, location);
		$C.posInd.redraw();

		var time1 = new Date();
		loggit("The block at " + location.x + ", " + location.y + ", " + location.z + " was removed in " + (time1 - time0) + " ms.");
	}
	else {
		loggit("Nothing to remove.");
	}
}

function drawAllBlocks () {
	var location = {
		x: 0,
		y: 0,
		z: 0
	}

	var gridPosition = 0;
	var coors = new Object();

	for (var i = 0; i < Field.length; i++) {
		location = {x: Field[i][0], y: Field[i][1], z: Field[i][2]};
		gridPosition = location.x * $C.gridDims.c + location.y;
		coors = GridField["x-" + gridPosition].coors;
		color = colorBlockNew(SwatchField[Field[i][3]][3]);
		canvasBlock(coors, location, color);
	}
}

function popField(x, y, z) {
	for (var i = 0; i < Field.length; i++) {
		// console.log(i);
		if (Field[i][0] == x && Field[i][1] == y && Field[i][2] == z) {
			Field.splice(i, 1);
			break;
		}
	}
}

<commit_msg>Fixed a minor bug in placeBlock which allowed blocks to be drawn over another block of the same color.<commit_after>/*
 * Nanoblok (Experimental) - Web-Based Graphical Editor for Game Sprite Development
 * http://code.google.com/p/nanoblok/
 * Copyright (c) 2009-2010 Alex Trujillo
 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
 *
 * Summary for block.js:
 * This file holds outdated SVG-based block rendering code. This stores data in the DOM, which uses more resources and is slower.
 * Soon to be deprecated.
 */

// Draws hexagonal points in iso perspective, based on hard coordinate and size of block. Depends on an orientation array (see block-grid.png in Nanoblok Extras). Returns static coordinates of where to draw blocks on the screen.
function hexiso (offset, blockSize) {
	// Builds an array of points that corresponds to an entire isometric block (six points), including center (7). Arrays for both X and Y coordinates. Offset added to hexagon proportions.
	var isoX = Array();
	isoX [1] = blockSize.half + offset.x;
	isoX [2] = blockSize.full + offset.x;
	isoX [3] = blockSize.full + offset.x;
	isoX [4] = blockSize.half + offset.x;
	isoX [5] = offset.x;
	isoX [6] = offset.x;
	isoX [7] = blockSize.half + offset.x;

	var isoY = Array();
	isoY [1] = offset.y;
	isoY [2] = blockSize.quarter + offset.y;
	isoY [3] = blockSize.third + offset.y;
	isoY [4] = blockSize.full + offset.y;
	isoY [5] = blockSize.third + offset.y;
	isoY [6] = blockSize.quarter + offset.y;
	isoY [7] = blockSize.half + offset.y;

	return {x: isoX, y: isoY};
}

// A very useful function to draw a set of coordinates in a hexagonal setup at a certain offset.
// Very necessary for an isometric setup such as this.
function canvasDrawSet (hexSet, offset, settings) {
	var canvas = null;

	if (settings.grid) {
		canvas = 'grids';
	}
	else if ($C.swatchActive) {
		canvas = 'colors';
	}
	else if ($C.selected.tool == "toolButtonSelect") {
		canvas = 'selection';
	}
	else {
		canvas = 'blocks';
	}

	var ctx = context(canvas);

	ctx.globalAlpha = 1.0;

	var hexSpot = hexSet.pop();
	var coorSet = hexiso(offset, $C.blockSize);
	var offsY = -35;

	ctx.beginPath();
	ctx.moveTo(coorSet.x[hexSpot], coorSet.y[hexSpot] + offsY);

	var i = 0;

	while (i < hexSet.length || i == 7) {
		hexSpot = hexSet.pop();
		ctx.lineTo(coorSet.x[hexSpot], coorSet.y[hexSpot] + offsY);
		i = i++;
	}

	if (settings.closed) {
		ctx.closePath();
	}

	if (settings.stroke !== false) {
		ctx.strokeStyle = settings.stroke;
		ctx.stroke();
	}
	if (settings.fill !== false) {
		ctx.fillStyle = settings.fill;
		ctx.fill();
	}
}

// colorBlock uses a color object (with separate color values for each face as well as its lines) to provide different shading for block faces.

var cubeShift = {a: -20, b: -10, c: 0, d: -30};
var blokShift = {a: -40, b: -20, c: 0, d: -60};

function colorBlockNew (color) {
	var blockColors = new Object();
	var shift;

	if ($C.swatchActive) {
		shift = cubeShift;
	}
	else {
		shift = blokShift;
	}

	blockColors.left = "rgb(" + (color.r + shift.a) + ", " + (color.g + shift.a) + ", " + (color.b + shift.a) + ")";
	blockColors.right = "rgb(" + (color.r + shift.b) + ", " + (color.g + shift.b) + ", " + (color.b + shift.b) + ")";
	blockColors.top = "rgb(" + (color.r + shift.c) + ", " + (color.g + shift.c) + ", " + (color.b + shift.c) + ")";
	blockColors.inset = "rgb(" + (color.r + shift.d) + ", " + (color.g + shift.d) + ", " + (color.b + shift.d) + ")";

	return blockColors;
}

// Paints a block on the board with proper color and occlusion.
// Takes coors x/y for position, xyz location on the grid, $C, and the color object of the block.
// A color id can be converted into a color object using the colorBlock function.
function canvasBlock (position, location, color) {
	var adjustedPosition = {x: position.x, y: position.y - $C.blockSize.half * (location.z + 1)};

	if ($C.swatchActive) {
		Arr = SwatchGhost;
	}
	// else if ($C.selected.tool == "Select") {
	// 	Arr = SelectionVox;
	// }
	else {
		Arr = Voxel;
	}

	// Top side. Always placed, unless there's a block above it.
	if (Arr[location.x][location.y][location.z + 1] == -1) {
		canvasDrawSet([1, 6, 7, 2], adjustedPosition, {closed: true, fill: color.top, stroke: color.inset});
	}

	// Left side.
	if (Arr[location.x - 1][location.y][location.z] == -1
			&& Arr[location.x - 1][location.y + 1][location.z] == -1) {
		canvasDrawSet([6, 7, 4, 5], adjustedPosition, {closed: true, fill: color.left, stroke: color.inset});
	} else if (Arr[location.x - 1][location.y + 1][location.z] != -1
			&& Arr[location.x - 1][location.y][location.z] == -1) {
		canvasDrawSet([6, 7, 5], adjustedPosition, {closed: true, fill: color.left, stroke: color.inset});
	}

	// Right side.
	if (Arr[location.x][location.y + 1][location.z] == -1
			&& Arr[location.x - 1][location.y + 1][location.z] == -1) {
		canvasDrawSet([2, 7, 4, 3], adjustedPosition, {closed: true, fill: color.right, stroke: color.inset});
	} else if (Arr[location.x - 1][location.y + 1][location.z] != -1
			&& Arr[location.x][location.y + 1][location.z] == -1) {
		canvasDrawSet([2, 7, 3], adjustedPosition, {closed: true, fill: color.right, stroke: color.inset});
	}
}

// Calls canvasBlock to place a block on the grid.
function placeBlock (target) {
	// Make location object.
	var location = {
		x: GridField[target.id].x,
		y: GridField[target.id].y,
		z: $C.layerOffset.z
	}

	// If the same block already exists at the location being painted, don't paint over it again.
	if (Voxel[location.x][location.y][location.z] != -1) {
		if (Field[Voxel[location.x][location.y][location.z]][3] != $C.selected.color) {
			placeBlockDraw(target, location);
		}
		else {
			loggit("Same block already exists at location.");
		}
	}
	else {
		placeBlockDraw(target, location);
	}
}

function placeBlockDraw (target, location) {
	// Draw the actual block using coordinates using the location of the grid's tiles as a reference for pixel-placement for all the rest of the blocks (this is the first argument). The target.id should look something like "x-123".
	// colorBlock is used to turn the color index into a color object (with separate color values for each face as well as its lines)
	canvasBlock(GridField[target.id].coors, location, colorBlockNew(SwatchField[$C.selected.color][3]));

	// Record information in the Field array, which is for serialization.
	Field.push([location.x, location.y, location.z, $C.selected.color]);
	// As well as the Field index of the block internally using the Voxel array.
	Voxel[location.x][location.y][location.z] = Field.length - 1;

	// Let the user know they've placed a block.
	loggit("Block placed at " + location.x + ", " + location.y + ", " + location.z + ".")

	// Redraw the display so that this change shows up immediately.
	$C.posInd.redraw();
}

function removeBlock (target) {
	var time0 = new Date();

	var location = {
		x: GridField[target.id].x,
		y: GridField[target.id].y,
		z: 0 + $C.layerOffset.z // forgot why I put a zero here.
	}

	if (Voxel[location.x][location.y][location.z] != -1) {
		Voxel[location.x][location.y][location.z] = -1;
		popField(location.x, location.y, location.z);

		$C.posInd.clearBlocks();
		drawAllBlocks();
		// canvasBlockRemove(GridField[target.id].coors, location);
		$C.posInd.redraw();

		var time1 = new Date();
		loggit("The block at " + location.x + ", " + location.y + ", " + location.z + " was removed in " + (time1 - time0) + " ms.");
	}
	else {
		loggit("Nothing to remove.");
	}
}

function drawAllBlocks () {
	var location = {
		x: 0,
		y: 0,
		z: 0
	}

	var gridPosition = 0;
	var coors = new Object();

	for (var i = 0; i < Field.length; i++) {
		location = {x: Field[i][0], y: Field[i][1], z: Field[i][2]};
		gridPosition = location.x * $C.gridDims.c + location.y;
		coors = GridField["x-" + gridPosition].coors;
		color = colorBlockNew(SwatchField[Field[i][3]][3]);
		canvasBlock(coors, location, color);
	}
}

function popField(x, y, z) {
	for (var i = 0; i < Field.length; i++) {
		// console.log(i);
		if (Field[i][0] == x && Field[i][1] == y && Field[i][2] == z) {
			Field.splice(i, 1);
			break;
		}
	}
}

<|endoftext|>


<commit_before>//Author: Ribo Mo
(function ($) {
    "use strict";

    var GRID_WIDTH = 200;

    $.fn.nestedGrid = function (options) {
        $.fn.nestedGrid.Options = $.extend($.fn.nestedGrid.defaultOptions, options);
        this.each(function () {
            var $table = $(this);
            setGrid($table, options);
        });
    };

    // Provide default options for plugin
    $.fn.nestedGrid.defaultOptions = {
        ajax: {
            method: "post",
            dataType: "json"
        }
    };

    // Reload the grid
    $.fn.nestedGrid.reloadGrid = function ($container) {
        var data = $container.children().data("data");
        var fields = $container.children().data("fields");
        var tableName = $container.children().data("table");
        var newGrid = new Grid(data, fields, tableName);
        $container.html(newGrid.$grid);
    };

    // Pass in selector and this function will make it a grid!
    var setGrid = function ($table, userSettings) {
        var options = $.fn.nestedGrid.Options;

        // generate from AJAX
        if (!options.data) {
            var request = $.ajax(options.ajax);
            return request.success(function (result) {
                var grid = new Grid(result.data, result.fields, result.tableName);
                $table.html(grid.$grid);
            });
        }
        else {
            var grid = new Grid(userSettings.data, userSettings.fields, userSettings.tableName);
            $table.html(grid.$grid);
        }
    };

    function Grid(data, fields, tableName) {
        this.data = data;
        this.fields = fields;
        this.tableName = tableName;
        this.$grid = null;
        this.init();
    }

    Grid.prototype = {
        init: function () {
            this.$grid = $("<table>");
            this.$grid.append(this.createHeader());
            this.$grid.append(this.createBody());
            this.$grid.on("click", ".text-field", this.editRow);

            this.$grid.addClass("nestedGrid");
            this.$grid.data("table", this.tableName);
            this.$grid.data("data", this.data);
            this.$grid.data("fields", this.fields);
        },

        createHeader: function () {
            var fields = showTopLevelFields(this.fields);
            var $fieldData = [];
            $fieldData.push($("<th>"));
            for (var i = 0; i < fields.length; i++) {
                var $field = $("<th>").append(fields[i]);
                $field.width(GRID_WIDTH);
                $fieldData.push($field);
            }
            $fieldData.push($("<th>").append("action"));
            return $("<thead><tr>").append($fieldData);
        },

        createBody: function () {
            var rows = [];
            for (var i = 0; i < this.data.length; i++) {
                var row = new Row(this.data[i], this.fields, this.$grid);

                // Add Strap
                if (i % 2 == 0) {
                    row.$row.addClass("odd-row");
                } else {
                    row.$row.addClass("even-row");
                }
                rows = rows.concat(rows, row.getRow());
            }
            return $("<tbody>").append(rows);
        },

        // Allow user to edit data in the row
        editRow: function () {
            var removeEditing = function(){
                $(this).find(".text-field").each(function () {
                    var textBox = $(this).find("input");
                    $(this).text(textBox.val());
                    textBox.remove();
                });
                $(this).removeClass("editing");
            };

            var $row = $(this).parent();

            if ($row.hasClass("editing")) {
            } else {
                // If there's another row in editing status, cancel the editing state.
                // Make sure editing row is unique.
                var $grid = $row.parent().parent();
                var $editingRows = $grid.find(".editing");
                if ($editingRows.length !== 0) {
                    $editingRows.each(removeEditing);
                    $("html").off("click");
                }

                // Replace text with input
                $row.find(".text-field").each(function () {
                    if (($(this)).has("input").length === 0) {
                        $row.addClass("editing");
                        var text = $(this).text();
                        var $textBox = $("<input>").attr("type", "text").attr("value", text);
                        $(this).text('').append($textBox);
                    }
                });

                // When user clicked elsewhere, remove editing status.
                $("html").click(function (event) {
                    if (!$(event.target).closest(".nestedGrid").length && !$(event.target).is(".nestedGrid")) {
                        var $that = $(".nestedGrid").find("tr");
                        removeEditing.bind($that)();
                        $("html").off("click");
                    }
                });
            }
        }

    };

    // Constructor for Row
    function Row(data, fields, $grid) {
        this.data = data;
        this.fields = fields;
        this.$grid = $grid;
        this.$row = null;
        this.topLevelFields = null;
        this.$subgrid = null;
        this.init();
    }

    Row.prototype = {
        init: function () {
            this.$row = $("<tr>");
            this.topLevelFields = showTopLevelFields(this.fields);

            var $rowData = [];
            var $subgridTrigger = this.createSubgridTrigger();
            $rowData.push($subgridTrigger);
            $rowData = $rowData.concat(this.createCells());
            $rowData.push(this.createSaveDeleteField());
            this.$row.append($rowData);

            // Event delegation
            this.$row.on("click", ".save-link", this.editEvent().saveClickEvent);
            this.$row.on("click", ".delete-link", this.editEvent().deleteClickEvent);

            // Add subgrid
            if (this.isSubgridExist()) {
                this.$subgrid = this.createSubgrid();
                var that = this;
                this.$row.on("click", ".subgrid-trigger", function () {
                    that.$subgrid.fadeToggle();
                    var $this = $(this);
                    if ($this.hasClass("expand")) {
                        $this.removeClass("expand");
                        $this.addClass("collapse");
                    }
                    else {
                        $this.removeClass("collapse");
                        $this.addClass("expand");
                    }
                });
            }

            this.$row.data("data", this.data);
        },

        //Return $row selector and $subgrid selector
        getRow: function () {
            var result = [];
            result.push(this.$row);
            if (this.$subgrid) {
                result.push(this.$subgrid);
            }
            return result;
        },

        // Add subgrid to existing grid
        createSubgrid: function () {
            var that = this;
            var subgridInfo = function (fields) {
                var result = {
                    fields: [],
                    table: []
                };
                for (var i = 0; i < fields.length; i++) {
                    if (fields[i].indexOf(".") !== -1) {
                        var data = fields[i].split(".");
                        result["fields"].push(data[1]);
                        if (result["table"].indexOf(data[0]) === -1) {
                            result["table"] = data[0];
                        }
                    }
                }
                return result;
            }(this.fields);
            var maxCol = function () {
                var maxCol = 0;
                that.$row.find('td').each(function () {
                    maxCol += 1;
                });
                return maxCol;
            }();
            var subgridData = this.data[this.getSubgridName()[0]];
            var subgrid = new Grid(subgridData, subgridInfo.fields, subgridInfo.table);
            subgrid.$grid.addClass("subgrid");
            var $insertCell = $("<td>").append(subgrid.$grid).attr("colspan", maxCol);
            var $insertRow = $("<tr>").append($insertCell).addClass("collapse");
            $insertRow.hide();
            return $insertRow;
        },

        // Return all the data contain in the fields
        createCells: function () {
            var fields = this.topLevelFields;
            var $result = [];
            for (var i = 0; i < fields.length; i++) {
                var $fieldData = $("<td>").append(this.data[fields[i]]).addClass("text-field");
                $result.push($fieldData);
            }
            return $result;
        },

        // Add trigger
        createSubgridTrigger: function () {
            if (this.isSubgridExist()) {
                var $expand = $("<div>").addClass("subgrid-trigger").addClass("expand").addClass("trigger-placeholder");
                return $("<td>").append($expand);
            }
            else {
                return $("<td>").append($("<div>").addClass("trigger-placeholder"));
            }
        },

        createSaveDeleteField: function () {
            var $editField = $("<td>");
            var $save = $("<a>").append("save").attr("href", "#").addClass("save-link");
            var $delete = $("<a>").append("delete").attr("href", "#").addClass("delete-link");
            $editField.append($save).append(" ").append($delete);
            return $editField;
        },

        // Event Handlers for edit and delete button
        editEvent: function () {
            var that = this;
            var getData = function () {
                var obj = {};
                var i = 0;
                that.$row.find("td.data").each(function () {
                    obj[that.topLevelFields[i]] = $(this).text();
                    i++;
                });
                return obj;
            };
            var getTableName = function () {
                return that.$row.parent().parent().data("table");
            };
            var options = $.fn.nestedGrid.Options;
            return {
                saveClickEvent: function () {
                    var newData = getData();
                    var object = {
                        purpose: "edit",
                        tableName: getTableName(),
                        new: newData,
                        old: that.data
                    };
                    console.log(object.new);
                    console.log(object.old);
                    //Pass data to server
                    $.ajax({
                        method: "post",
                        url: options.ajax.url,
                        dataType: "json",
                        data: JSON.stringify(object)
                    });
                    that.reloadRow();
                },
                deleteClickEvent: function () {
                    var object = {
                        purpose: "delete",
                        tableName: getTableName(),
                        old: that.data
                    };
                    console.log(object.old);
                    that.$row.remove();
                    $.ajax({
                        method: "post",
                        url: options.ajax.url,
                        dataType: "json",
                        data: JSON.stringify(object)
                    });
                    $.fn.nestedGrid.reloadGrid(that.$grid.parent());
                }
            }
        },

        getSubgridName: function () {
            var result = [];
            for (var i = 0; i < this.fields.length; i++) {
                if (this.fields[i].indexOf(".") !== -1) {
                    var data = this.fields[i].split(".");
                    if (result.indexOf(data[0]) === -1) {
                        result.push(data[0]);
                    }
                }
            }
            return result;
        },

        isSubgridExist: function () {
            return this.data[this.getSubgridName()[0]] !== undefined;
        },

        reloadRow: function () {
            var row = new Row(this.data, this.fields, this.$grid);
            this.$row.html(row.$row.html());
            this.$subgrid.html(row.$subgrid.html());
        }
    };

    $.fn.nestedGrid.reloadGrid = function ($container) {
        var data = $container.children().data("data");
        var fields = $container.children().data("fields");
        var tableName = $container.children().data("table");
        var newGrid = new Grid(data, fields, tableName);
        $container.html(newGrid.$grid);
    };

    //Only show fields that are in top level
    function showTopLevelFields(fields) {
        var result = [];
        for (var i = 0; i < fields.length; i++) {
            if (fields[i].indexOf(".") === -1) {
                result.push(fields[i]);
            }
        }
        return result;
    }
}(jQuery));<commit_msg>AJAX and refresh fix<commit_after>//Author: Ribo Mo
(function ($) {
    "use strict";

    var GRID_WIDTH = 200;

    $.fn.nestedGrid = function (options) {
        $.fn.nestedGrid.Options = $.extend($.fn.nestedGrid.defaultOptions, options);
        this.each(function () {
            var $table = $(this);
            $(this).data("settings", options);
            setGrid($table, options);
        });
    };

    // Provide default options for plugin
    $.fn.nestedGrid.defaultOptions = {
        ajax: {
            method: "post",
            dataType: "json"
        }
    };

    // Reload the grid
    $.fn.nestedGrid.reloadGrid = function ($container) {
        setGrid($container, $container.data("settings"));
    };

    // Pass in selector and this function will make it a grid!
    var setGrid = function ($table, userSettings) {
        var options = $.fn.nestedGrid.Options;
        // generate from AJAX
        if (!options.data) {
            var request = $.ajax(options.ajax);
            return request.success(function (result) {
                var grid = new Grid(result.data, result.fields, result.tableName);
                $table.html(grid.$grid);
            });
        }
        else {
            var grid = new Grid(userSettings.data, userSettings.fields, userSettings.tableName);
            $table.html(grid.$grid);
        }
    };

    function Grid(data, fields, tableName) {
        this.data = data;
        this.fields = fields;
        this.tableName = tableName;
        this.$grid = null;
        this.init();
    }

    Grid.prototype = {
        init: function () {
            this.$grid = $("<table>");
            this.$grid.append(this.createHeader());
            this.$grid.append(this.createBody());
            this.$grid.on("click", ".text-field", this.editRow);

            this.$grid.addClass("nestedGrid");
            this.$grid.data("table", this.tableName);
            this.$grid.data("data", this.data);
            this.$grid.data("fields", this.fields);
        },

        createHeader: function () {
            var fields = showTopLevelFields(this.fields);
            var $fieldData = [];
            $fieldData.push($("<th>"));
            for (var i = 0; i < fields.length; i++) {
                var $field = $("<th>").append(fields[i]);
                $field.width(GRID_WIDTH);
                $fieldData.push($field);
            }
            $fieldData.push($("<th>").append("action"));
            return $("<thead><tr>").append($fieldData);
        },

        createBody: function () {
            var rows = [];
            for (var i = 0; i < this.data.length; i++) {
                var row = new Row(this.data[i], this.fields, this.$grid);

                // Add Strap
                if (i % 2 == 0) {
                    row.$row.addClass("odd-row");
                } else {
                    row.$row.addClass("even-row");
                }
                rows = rows.concat(rows, row.getRow());
            }
            return $("<tbody>").append(rows);
        },

        // Allow user to edit data in the row
        editRow: function () {
            var removeEditing = function(){
                $(this).find(".text-field").each(function () {
                    var textBox = $(this).find("input");
                    $(this).text(textBox.val());
                    textBox.remove();
                });
                $(this).removeClass("editing");
            };

            var $row = $(this).parent();

            if (!$row.hasClass("editing")){
                // If there's another row in editing status, cancel the editing state.
                // Make sure editing row is unique.
                var $grid = $row.parent().parent();
                var $editingRows = $grid.find(".editing");
                if ($editingRows.length !== 0) {
                    $editingRows.each(removeEditing);
                    $("html").off("click");
                }

                // Replace text with input
                $row.find(".text-field").each(function () {
                    if (($(this)).has("input").length === 0) {
                        $row.addClass("editing");
                        var text = $(this).text();
                        var $textBox = $("<input>").attr("type", "text").attr("value", text);
                        $(this).text('').append($textBox);
                    }
                });

                // When user clicked elsewhere, remove editing status.
                $("html").click(function (event) {
                    if (!$(event.target).closest(".text-field").length && !$(event.target).is(".text-field")) {
                        var $that = $(".nestedGrid").find("tr");
                        removeEditing.bind($that)();
                        $("html").off("click");
                    }
                });
            }
        }

    };

    // Constructor for Row
    function Row(data, fields, $grid) {
        this.data = data;
        this.fields = fields;
        this.$grid = $grid;
        this.$row = null;
        this.topLevelFields = null;
        this.$subgrid = null;
        this.init();
    }

    Row.prototype = {
        init: function () {
            this.$row = $("<tr>");
            this.topLevelFields = showTopLevelFields(this.fields);

            var $rowData = [];
            var $subgridTrigger = this.createSubgridTrigger();
            $rowData.push($subgridTrigger);
            $rowData = $rowData.concat(this.createCells());
            $rowData.push(this.createSaveDeleteField());
            this.$row.append($rowData);

            // Event delegation
            this.$row.on("click", ".save-link", this.editEvent().saveClickEvent);
            this.$row.on("click", ".delete-link", this.editEvent().deleteClickEvent);

            // Add subgrid
            if (this.isSubgridExist()) {
                this.$subgrid = this.createSubgrid();
                var that = this;
                this.$row.on("click", ".subgrid-trigger", function () {
                    that.$subgrid.fadeToggle();
                    var $this = $(this);
                    if ($this.hasClass("expand")) {
                        $this.removeClass("expand");
                        $this.addClass("collapse");
                    }
                    else {
                        $this.removeClass("collapse");
                        $this.addClass("expand");
                    }
                });
            }

            this.$row.data("data", this.data);
        },

        //Return $row selector and $subgrid selector
        getRow: function () {
            var result = [];
            result.push(this.$row);
            if (this.$subgrid) {
                result.push(this.$subgrid);
            }
            return result;
        },

        // Add subgrid to existing grid
        createSubgrid: function () {
            var that = this;
            var subgridInfo = function (fields) {
                var result = {
                    fields: [],
                    table: []
                };
                for (var i = 0; i < fields.length; i++) {
                    if (fields[i].indexOf(".") !== -1) {
                        var data = fields[i].split(".");
                        result["fields"].push(data[1]);
                        if (result["table"].indexOf(data[0]) === -1) {
                            result["table"] = data[0];
                        }
                    }
                }
                return result;
            }(this.fields);
            var maxCol = function () {
                var maxCol = 0;
                that.$row.find('td').each(function () {
                    maxCol += 1;
                });
                return maxCol;
            }();
            var subgridData = this.data[this.getSubgridName()[0]];
            var subgrid = new Grid(subgridData, subgridInfo.fields, subgridInfo.table);
            subgrid.$grid.addClass("subgrid");
            var $insertCell = $("<td>").append(subgrid.$grid).attr("colspan", maxCol);
            var $insertRow = $("<tr>").append($insertCell).addClass("collapse");
            $insertRow.hide();
            return $insertRow;
        },

        // Return all the data contain in the fields
        createCells: function () {
            var fields = this.topLevelFields;
            var $result = [];
            for (var i = 0; i < fields.length; i++) {
                var $fieldData = $("<td>").append(this.data[fields[i]]).addClass("text-field").addClass("data");
                $result.push($fieldData);
            }
            return $result;
        },

        // Add trigger
        createSubgridTrigger: function () {
            if (this.isSubgridExist()) {
                var $expand = $("<div>").addClass("subgrid-trigger").addClass("expand").addClass("trigger-placeholder");
                return $("<td>").append($expand);
            }
            else {
                return $("<td>").append($("<div>").addClass("trigger-placeholder"));
            }
        },

        createSaveDeleteField: function () {
            var $editField = $("<td>");
            var $save = $("<a>").append("save").attr("href", "#").addClass("save-link");
            var $delete = $("<a>").append("delete").attr("href", "#").addClass("delete-link");
            $editField.append($save).append(" ").append($delete);
            return $editField;
        },

        // Event Handlers for edit and delete button
        editEvent: function () {
            var that = this;

            var getData = function () {
                var obj = {};
                var i = 0;
                $(this).closest("tr").find("td.data").each(function () {
                    obj[that.topLevelFields[i]] = $(this).text();
                    i++;
                });
                return obj;
            };

            var getTableName = function () {
                return that.$row.parent().parent().data("table");
            };
            var options = $.fn.nestedGrid.Options;
            return {
                saveClickEvent: function (event) {
                    event.preventDefault();
                    var newData = getData.bind(this)();
                    var object = {
                        purpose: "edit",
                        tableName: getTableName(),
                        new: newData,
                        old: that.data
                    };
                    console.log(object.new);
                    console.log(object.old);
                    //Pass data to server
                    $.ajax({
                        method: "post",
                        url: options.ajax.url,
                        dataType: "json",
                        data: JSON.stringify(object),

                        success: function(){
                            //Refresh the grid
                            var $topGrid = $(event.target).parents(".nestedGrid").last();
                            $.fn.nestedGrid.reloadGrid($topGrid);
                        }
                    });
                },
                deleteClickEvent: function (event) {
                    event.preventDefault();
                    var object = {
                        purpose: "delete",
                        tableName: getTableName(),
                        old: that.data
                    };
                    console.log(object.old);
                    that.$row.remove();
                    $.ajax({
                        method: "post",
                        url: options.ajax.url,
                        dataType: "json",
                        data: JSON.stringify(object)
                    });
                    $.fn.nestedGrid.reloadGrid(that.$grid.parent());
                }
            }
        },

        getSubgridName: function () {
            var result = [];
            for (var i = 0; i < this.fields.length; i++) {
                if (this.fields[i].indexOf(".") !== -1) {
                    var data = this.fields[i].split(".");
                    if (result.indexOf(data[0]) === -1) {
                        result.push(data[0]);
                    }
                }
            }
            return result;
        },

        isSubgridExist: function () {
            return this.data[this.getSubgridName()[0]] !== undefined;
        }
    };

    //Only show fields that are in top level
    function showTopLevelFields(fields) {
        var result = [];
        for (var i = 0; i < fields.length; i++) {
            if (fields[i].indexOf(".") === -1) {
                result.push(fields[i]);
            }
        }
        return result;
    }
}(jQuery));<|endoftext|>