{"version":3,"sources":["components/Explanation.tsx","components/StatsBar.tsx","components/SQLControl.tsx","components/FAQ.tsx","App.tsx","lib/TabManager.ts","lib/Database.ts","lib/DataWindowManager.ts","lib/RootWindowManager.ts","index.tsx"],"names":["Explanation","react_default","a","createElement","className","href","createTab","window","windowManager","createNewTab","sqlTextArea","console","log","StatsBar","_ref","availableChars","onClick","PREFILLS","create","insert","select","SQLControl","state","value","lastWindowData","textAreaText","lastQueryOutput","onSubmit","_this","setState","message","setTimeout","result","submitSQL","prefill","key","onSQLChange","event","target","this","output","error","rows","values","length","columns","map","col","row","i","onChange","placeholder","disabled","renderQueryOutput","Component","FAQ","showing","toggleVisibility","arrow","renderFAQ","React","App","availableCharacters","tabManager","components_Explanation","src_components_StatsBar","src_components_SQLControl","src_components_FAQ","TabManager","tabs","arguments","undefined","Object","classCallCheck","data","requiredCharacters","JSON","stringify","dataString","chunks","shortChunkSize","Math","floor","longChunks","chunkLength","chunk","slice","document","title","dataStrings","filter","tab","json","join","parse","Database","persistence","sqlite","initSqlJs","locateFile","filename","concat","then","query","db","readDB","exec","writeDB","dbObject","dbDataArray","export","compressedString","toBinString","pako","deflate","base64Data","btoa","canFit","pers","Error","write","read","atob","inflate","toBinArray","arr","uarr","Uint8Array","strings","subarr","subarray","push","String","fromCharCode","apply","str","l","charCodeAt","DataWindowManager","body","innerHTML","e","preventDefault","name","location","reload","getElementById","style","display","RootWindowManager","run","tabList","database","setTabList","sql","runQuery","err","win","recoveredTabs","opener","unshift","setTabs","newWindow","open","getElementsByTagName","sqlTextAreaText","WINDOW_NAME_PREFIX","alert","recoverTabs","ReactDOM","render","src_App_0"],"mappings":"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","file":"static/js/main.26079986.chunk.js","sourcesContent":["import React from 'react';\n\nconst Explanation: React.FC = () => {\n  return (\n    <div className='Explanation'>\n      <h1>TabDB</h1>\n      <p>Do you treat your browser tabs like a database?  Well make them queryable with <b>TabDB</b>!  Another thoroughly useful... thing... by <a href='https://twitter.com/kkuchta'>@kkuchta.</a></p>\n    </div>\n  );\n}\n\nexport default Explanation;\n","import React from 'react';\nimport './StatsBar.css';\n\nconst createTab = () => {\n  window.windowManager.createNewTab({sqlTextArea: 'dfsfdsa'});\n  console.log('here')\n}\n\nconst StatsBar: React.FC<{availableChars: number}> = ({ availableChars }) => {\n  return (\n    <div className='StatsBar'>\n      <span className='availableCharacters'>Available Characters: {availableChars}</span>\n      <button onClick={createTab} className='addTab'>Click here to add a tab to increase characters</button>\n    </div>\n  );\n}\n\nexport default StatsBar;\n","import React, { Component } from 'react';\nimport { QueryResult } from '../lib/Database'\nimport './SQLControl.css';\n\nconst PREFILLS = {\n  create: \"CREATE TABLE users (age int, name string);\",\n  insert: \"INSERT INTO users VALUES (20, 'bill');\\nINSERT INTO users VALUES (32, 'ann');\",\n  select: \"SELECT * FROM users;\"\n} as any;\n\nclass SQLControl extends Component {\n  state = {\n    value: window.lastWindowData ? window.lastWindowData.textAreaText : '',\n    lastQueryOutput: null\n  }\n\n  onSubmit = () => {\n    const { value } = this.state;\n    this.setState({ lastQueryOutput: { message: 'running...' } });\n\n    // Add a half-second delay so it's clear something's happening.  I'm too\n    // lazy to make the UI reflect this in a better way.\n    setTimeout( () => {\n      const result = window.windowManager.submitSQL(value);\n      this.setState({ lastQueryOutput: result })\n      console.log(this.state.value)\n    }, 300 );\n  }\n  prefill = (key: string) => () => this.setState({ value: PREFILLS[key] });\n  onSQLChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n\n    // We need this value for when we open a new tab.  I don't feel like adding\n    // a proper state management system like Redux just to move one variable on\n    // a joke project, so let's just yolo a global variable.\n    //window.sql = event.target.value;\n\n    this.setState({value: event.target.value});\n  };\n  renderQueryOutput() {\n    console.log(\"Rendering output\", this.state.lastQueryOutput)\n    const output = this.state.lastQueryOutput as QueryResult | null;\n    if (output == null) {\n      return <div>&lt;--- Run a query to get some output</div>;\n    } else if (output.error != null) {\n      return <div>Error: {output.error}</div>;\n    } else if (output.message != null) {\n      return <div>{output.message}</div>;\n    } else if (output.rows != null && output.rows.values.length > 0) {\n      return (\n        <table>\n          <thead><tr>{ output.rows.columns.map((col) => <th key={col}>{col}</th>) }</tr></thead>\n          <tbody>\n            { output.rows.values.map(\n              (row, i) => <tr key={i}>\n                { row.map(\n                  (value, i) => <td key={i}>{value}</td>\n                ) }\n              </tr>\n            ) }\n          </tbody>\n        </table>\n      );\n    } else {\n      return <div>Query ran successfully</div>;\n    }\n  }\n  render() {\n    const { value } = this.state;\n    return (\n      <div className='SQLControl'>\n        <div className='input'>\n          <textarea\n            value={value}\n            onChange={this.onSQLChange}\n            placeholder='Type SQL here, or use the buttons below to quick-fill common queries.'\n          />\n          <button className='runSQL' disabled={value == null || value === ''} onClick={this.onSubmit}>Run SQL</button>\n          <div className='prefillButtons'>\n            <span className='prefillLabel'>Quick sql samples:</span>\n            <button onClick={this.prefill('create')}>Create</button>\n            <button onClick={this.prefill('insert')}>Insert</button>\n            <button onClick={this.prefill('select')}>Select</button>\n          </div>\n        </div>\n        <div className='queryOutput'>\n          { this.renderQueryOutput() }\n        </div>\n        <br />\n        <br />\n      </div>\n    );\n  }\n}\n\nexport default SQLControl;\n","import React from 'react';\nimport './FAQ.css';\n\nclass FAQ extends React.Component {\n  state = {\n    showing: false\n  }\n  toggleVisibility = () => {\n    this.setState({ showing: !this.state.showing });\n  }\n  renderFAQ() {\n    return (\n      <ul>\n        <li>\n          <span className='question'>How dare you?</span>\n          I guess that <i>is</i> technically a question I get frequently.\n        </li>\n        <li>\n          <span className='question'>Ok, what's it actually doing?</span>\n          Every time you run an SQL query, it grabs all the data stored in the neighboring tabs' titles, concatenates it, unzips it, and loads it into an in-memory sqlite database.  It then runs the command, dumps the db state to a string, zips it up, and spreads it out across the available tabs.\n        </li>\n        <li>\n          <span className='question'>To reiterate: How dare you?</span>\n          Someone <a href='https://twitter.com/jbrodley/status/1152411896667967490'>jokingly tweeted about using tabs as a database</a> and I thought \"how hard could it be?\"\n        </li>\n        <li>\n          <span className='question'>Ok, so why is this a good idea?</span>\n          A <i>what</i>?\n        </li>\n        <li>\n          <span className='question'>Like, when would you use this in real life?</span>\n          I don't understand the question.\n        </li>\n        <li>\n          <span className='question'>Ok, so how'd you open tabs in the background reliably?</span>\n          I didn't.  Browsers *really* frown on pop-unders.  Instead, we open a new tab in the foreground, copy over the UI to it, then ditch the UI to the current tab (which is now in the background).\n        </li>\n        <li>\n          <span className='question'>How are the tabs communicating with each other?</span>\n          The \"root\" tab was always opened by the most recent \"data\" tab, so the root tab can grab a reference to that tab with <code>window.opener</code>.  Likewise, we can get the <i>next</i> data tab with <code>window.opener.opener</code>, and so on to get all data tabs.\n        </li>\n        <li>\n          <span className='question'>I think I actually have a legitimate use case for this</span>\n          Please reevaluate your life choices.\n        </li>\n        <li>\n          <span className='question'>Who's responsible for this nonsense?</span>\n          Blame <a href='https://twitter.com/kkuchta'>@kkuchta</a>.  The source code (not appropriate for small children) is on <a href='https://github.com/kkuchta/tabdb'>Github</a>.\n        </li>\n      </ul>\n    );\n  }\n  render() {\n    const { showing } = this.state;\n    const arrow = showing ? '↑'  : '↓';\n    return (\n      <div className='FAQ'>\n        <div className='faqHideShow' onClick={this.toggleVisibility}>FAQ {arrow}</div>\n        { showing && this.renderFAQ() }\n      </div>\n    );\n  }\n}\n\nexport default FAQ;\n","import React from 'react';\nimport Explanation from './components/Explanation';\nimport StatsBar from './components/StatsBar';\nimport SQLControl from './components/SQLControl';\nimport FAQ from './components/FAQ';\nimport './App.css';\n\nconst App: React.FC = () => {\n  // This doesn't change after the page is loaded\n  const availableCharacters = window.windowManager.tabManager.availableCharacters();\n\n  return (\n    <div className=\"App\">\n      <Explanation />\n      <StatsBar availableChars={availableCharacters}/>\n      { availableCharacters > 0 && <SQLControl /> }\n      { availableCharacters > 0 && <FAQ /> }\n      { availableCharacters <= 0 && <div>\n        Click that button a few times to add storage space to your db!\n      </div> }\n    </div>\n  );\n}\n\nexport default App;\n","const MAX_TAB_CHARS = 100;\n\n// Reads and writes data from a list of tabs\nexport default class TabManager {\n  tabs: Window[]\n  constructor(tabs = []) {\n    this.tabs = tabs;\n  }\n\n  setTabs(tabs: Window[]) {\n    this.tabs = tabs;\n  }\n  canFit(data: any) {\n    return this.requiredCharacters(data) < this.availableCharacters();\n  }\n\n  requiredCharacters(data: any) { return JSON.stringify(data).length }\n  availableCharacters() { return this.tabs.length * MAX_TAB_CHARS }\n\n  write(data: any) {\n    let dataString = JSON.stringify(data);\n\n    // Split data into to N chunks, as evenly as possible.\n    const length = dataString.length;\n    const chunks = this.tabs.length;\n    const shortChunkSize = Math.floor(length / chunks);\n    let longChunks = length % chunks;\n    for (let i = 0; i < chunks; i++) {\n      const chunkLength = longChunks > 0 ? shortChunkSize + 1 : shortChunkSize;\n      longChunks--;\n      const chunk = dataString.slice(0, chunkLength);\n      dataString = dataString.slice(chunkLength);\n      this.tabs[i].document.title = chunk === \"\" ? document.title : chunk;\n    }\n  }\n\n  read() {\n    // Filter out empty dbs\n    const dataStrings = this.tabs\n      .filter((tab) => tab.document.title !== window.document.title)\n      .map((tab) => tab.document.title);\n    if (dataStrings.length === 0) { return null }\n    const json = dataStrings.join('');\n    console.log(\"json=\", json);\n    return JSON.parse(json);\n  }\n\n}\n","import pako from 'pako';\nimport initSqlJs from 'sql.js';\n\n// A database that will, when queried: load the db, execute the query, and then\n// write it back out.  Expects a persistence mechanism with `read()`, `write(data)`,\n// and `canFit(data)` methods.\ninterface Persistence {\n  write: (db: string) => void;\n  canFit: (db: string) => boolean;\n  read: () => string;\n}\n\nexport interface QueryResult {\n  readonly error?: string;\n  readonly message?: string;\n  readonly rows?: {\n    columns: string[];\n    values: string[][];\n  }\n}\n\nexport default class Database {\n  persistence: Persistence;\n  sqlite: any;\n  constructor(persistence: any) {\n    this.persistence = persistence;\n    this.sqlite = null;\n    // TODO: actually wait for this to load.\n    const config = {\n      locateFile: (filename: string) => `/${filename}`\n    }\n    initSqlJs(config).then(sqlite => { this.sqlite = sqlite; });\n  }\n  runQuery(query: string): QueryResult {\n    const db = this.readDB();\n    const result = db.exec(query);\n    this.writeDB(db);\n    // TODO: free db\n    //\n    if (result[0]) { return { rows: result[0] } }\n    return {};\n  }\n  writeDB(dbObject: any) {\n    // Dump the entire db state.\n    const dbDataArray = dbObject.export();\n\n    // A db with 1 table and 2 rows is 8k long, mosty empty.  Let's compress it\n    // before trying to save it to tabs.\n    const compressedString = this.toBinString(pako.deflate(dbDataArray));\n\n    // Base64 encode it because weird utf-8 characters won't save to the dom and\n    // come back the same.\n    const base64Data = btoa(compressedString);\n    if (this.persistence.canFit(base64Data)) {\n      this.persistence.write(base64Data);\n    } else {\n      // TODO: throw this from the persistence object, not from here.\n      interface Foo {\n        requiredCharacters: (x: any) => number;\n        availableCharacters: () => number;\n      };\n      const pers = this.persistence as unknown as Foo;\n      throw new Error(`Not enough space to execute that query.  We'd need ${pers.requiredCharacters(base64Data)} characters of space, but we only have ${pers.availableCharacters()}.  Open more tabs to increase space!`);\n    }\n  }\n\n  readDB() {\n    const base64Data = this.persistence.read();\n    // If we read nothing from the tabs, create a new db.\n    if (base64Data == null) { return new this.sqlite.Database() }\n\n    const compressedString = atob(base64Data)\n    const dbDataArray = pako.inflate(this.toBinArray(compressedString))\n    return new this.sqlite.Database(dbDataArray);\n  }\n\n  // From https://github.com/kripken/sql.js/wiki/Persisting-a-Modified-Database\n  toBinString(arr: Uint8Array) {\n    var uarr = new Uint8Array(arr);\n    var strings = [], chunksize = 0xffff;\n    // There is a maximum stack size. We cannot call String.fromCharCode with as many arguments as we want\n    for (var i=0; i*chunksize < uarr.length; i++){\n      const subarr = uarr.subarray(i*chunksize, (i+1)*chunksize);\n      strings.push(String.fromCharCode.apply(null, subarr as unknown as number[]));\n    }\n    return strings.join('');\n  }\n\n  toBinArray(str: string) {\n    var l = str.length,\n      arr = new Uint8Array(l);\n    for (var i=0; i<l; i++) arr[i] = str.charCodeAt(i);\n    return arr;\n  }\n}\n","import RootWindowManager from './RootWindowManager'\n\nexport default class DataWindowManager {\n  run() {\n    document.body.innerHTML = \"<p>TabDB data window. We're only using this tab's title to store data.  You probably want to be on the tab that's displaying some UI (the 'root' tab) instead of here.  If you've lost your root tab, try clicking <a href='' onClick='window.windowManager.forceRoot(event)'>here</a></p>.\";\n  }\n  forceRoot(e: any) {\n    e.preventDefault();\n    window.name = '';\n    window.location.reload();\n  }\n  reroot({ textAreaText, tabs }: { textAreaText: string; tabs: Window[] }) {\n    console.log(\"rerooting - new tabs = \", tabs);\n    const textArea = window.document.getElementById('sqlTextArea') as HTMLTextAreaElement\n    textArea.value = textAreaText;\n    window.document.body.style.display = 'black';\n    (window.windowManager = new RootWindowManager(tabs))\n    window.windowManager.run();\n  }\n  submitSQL() {\n    console.log(\"submitSQL is a no-op on data pages (UI should be hidden anyway)\");\n  }\n}\n","import TabManager from './TabManager'\nimport Database, { QueryResult } from './Database'\nimport DataWindowManager from './DataWindowManager'\n\nconst WINDOW_NAME_PREFIX = \"tab_db_data_window_\";\nexport const WINDOW_ROOT_NAME = WINDOW_NAME_PREFIX + 'root';\n\nexport default class RootWindowManager{\n  tabManager: any;\n  database: any;\n  tabs: any;\n  constructor(tabList = [] as Window[]) {\n    console.log(\"constructing with \", tabList);\n    this.tabManager = new TabManager();\n    this.database = new Database(this.tabManager);\n    this.setTabList(tabList);\n  }\n  run() {\n    console.log(\"running\");\n    if (this.tabs == null) this.setTabList([]);\n  }\n  submitSQL(sql: string): QueryResult {\n    //const sqlArea = document.getElementById('sqlTextArea') as HTMLTextAreaElement\n    let result: QueryResult;\n    try {\n      result = this.database.runQuery(sql);\n    } catch(err) {\n      result = { error: err.message };\n    }\n    //if (result.rows == 0) return {};\n    //let resultText = result[0].columns.join(\"\\t\") + \"\\n\";\n    //resultText += result[0].values.map((row: string[]) => row.join(\"\\t\")).join(\"\\n\");\n    //console.log(resultText);\n\n    //document.getElementById('sqlTextArea').value = resultText;\n\n    console.log(\"result=\", result);\n    return result;\n  }\n  recoverTabs() {\n    console.log(\"recovering tabs\");\n    let win = window;\n    const recoveredTabs = [];\n    while((win = win.opener) != null) {\n      console.log(\"win = \", win.name);\n      recoveredTabs.unshift(win);\n    }\n    console.log(\"recovered \", recoveredTabs);\n    this.setTabList(recoveredTabs);\n    //this.tabs = recoveredTabs;\n  }\n  setTabList(tabList: Window[]) {\n    this.tabs = tabList;\n    this.tabManager.setTabs(tabList);\n  }\n\n  createNewTab() {\n    console.log(\"this.tabs = \", this.tabs);\n    const newWindow = window.open('#', WINDOW_NAME_PREFIX + this.tabs.length);\n    if (newWindow == null) {\n      alert(\"Couldn't open new tab for some reason?\");\n      return;\n    }\n\n    const sqlTextArea = document.getElementsByTagName('textarea')[0]\n    const sqlTextAreaText = sqlTextArea == null ? '' : sqlTextArea.value;\n\n    // TODO: handle tab closing - remove from tabarray and update stats\n    // TODO: do we actually need to pass tabs here?  Could just always recover\n    // tabs.\n    newWindow.lastWindowData = {\n      textAreaText: sqlTextAreaText,\n    }\n    window.name = newWindow.name;\n    newWindow.name = WINDOW_ROOT_NAME;\n    // This window is now a data window.\n    (window.windowManager = new DataWindowManager()).run();\n  }\n}\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport RootWindowManager, { WINDOW_ROOT_NAME } from './lib/RootWindowManager';\nimport DataWindowManager  from './lib/DataWindowManager';\n\ndeclare global {\n  interface Window {\n    windowManager: any;\n    lastWindowData: any;\n  }\n}\n\nconsole.log(RootWindowManager);\n\nif (window.name === '') window.name = WINDOW_ROOT_NAME;\nif (window.name === WINDOW_ROOT_NAME) {\n  console.log(\"Starting as root\");\n  (window.windowManager = new RootWindowManager()).run();\n  window.windowManager.recoverTabs();\n\n  ReactDOM.render(<App />, document.getElementById('root'));\n} else {\n  console.log(\"Starting as data\");\n  (window.windowManager = new DataWindowManager()).run();\n}\n"],"sourceRoot":""}