<|start_filename|>assets/kitchen.js<|end_filename|>
var port =3101;

var host = `http://localhost:${port}`;

window.SolidAppContext = {
  noAuth : host,
  webId : host + "/profile/card#me",
  app : host,
  webid : host + "/profile/card#me",
  scroll : 130 // for eyeFocus, should be height of top banner
}
window.$SolidTestEnvironment = {
  iconBase : "/common/icons/",
  originalIconBase : "/common/originalIcons/",
}


<|start_filename|>configs/css-config.json<|end_filename|>
{
  "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
  "import": [
    "files-scs:config/app/main/default.json",
    "files-scs:config/app/init/default.json",
    "files-scs:config/http/handler/default.json",
    "files-scs:config/http/middleware/websockets.json",
    "files-scs:config/http/server-factory/websockets.json",
    "files-scs:config/http/static/default.json",
    "files-scs:config/identity/email/default.json",
    "files-scs:config/identity/handler/default.json",
    "files-scs:config/identity/ownership/unsafe-no-check.json",
    "files-scs:config/identity/pod/static.json",
    "files-scs:config/ldp/authentication/dpop-bearer.json",
    "files-scs:config/ldp/authorization/webacl.json",
    "files-scs:config/ldp/handler/default.json",
    "files-scs:config/ldp/metadata-parser/default.json",
    "files-scs:config/ldp/metadata-writer/default.json",
    "files-scs:config/ldp/permissions/acl.json",
    "files-scs:config/storage/backend/file.json",
    "files-scs:config/storage/key-value/memory.json",
    "files-scs:config/storage/middleware/default.json",
    "files-scs:config/util/auxiliary/acl.json",
    "files-scs:config/util/identifiers/suffix.json",
    "files-scs:config/util/logging/winston.json",
    "files-scs:config/util/representation-conversion/converters/content-type-replacer.json",
    "files-scs:config/util/representation-conversion/converters/errors.json",
    "files-scs:config/util/representation-conversion/converters/markdown.json",
    "files-scs:config/util/representation-conversion/converters/quad-to-rdf.json",
    "files-scs:config/util/representation-conversion/converters/rdf-to-quad.json",
    "files-scs:config/util/resource-locker/memory.json",
    "files-scs:config/util/variables/default.json"
  ],
  "@graph": [
    {
      "comment": "Serve index.html within containers",
      "@id": "urn:solid-server:default:ResourceStore_Index",
      "@type": "IndexRepresentationStore"
    },

    {
      "comment": "Serve the Mashlib Data Browser as default container representation",
      "@id": "urn:solid-server:default:DefaultUiConverter",
      "@type": "ConstantConverter",
      "filePath": "./assets/kitchen.html",
      "contentType": "text/html",
      "options_minQuality": 1,
      "options_container": true,
      "options_document": false
    },
    {
      "comment": "Serve Mashlib && other static files.",
      "@id": "urn:solid-server:default:StaticAssetHandler",
      "@type": "StaticAssetHandler",
      "assets": [
        {
          "StaticAssetHandler:_assets_key": "/mashlib/",
          "StaticAssetHandler:_assets_value": "./node_modules/mashlib/dist"
        }
      ]
    },
    {
      "comment": "Serve Mashlib && other static files.",
      "@id": "urn:solid-server:default:StaticAssetHandler",
      "@type": "StaticAssetHandler",
      "assets": [
        {
          "StaticAssetHandler:_assets_key": "/common/",
          "StaticAssetHandler:_assets_value": "./assets"
        }
      ]
    },
    {
      "comment": "Handles data conversion when required.",
      "@id": "urn:solid-server:default:RepresentationConverter",
      "@type": "WaterfallHandler",
      "handlers": [
        { "@id": "urn:solid-server:default:DefaultUiConverter" },
        { "@id": "urn:solid-server:default:MarkdownToHtmlConverter" },
        {
          "@type": "IfNeededConverter",
          "comment": "Only continue converting if the requester cannot accept the available content type"
        },
        { "@id": "urn:solid-server:default:ContentTypeReplacer" },
        {
          "comment": "Automatically finds a path through a set of converters from one type to another.",
          "@id": "urn:solid-server:default:ChainedConverter",
          "@type": "ChainedConverter",
          "converters": [
            { "@id": "urn:solid-server:default:RdfToQuadConverter" },
            { "@id": "urn:solid-server:default:QuadToRdfConverter" },
            { "@id": "urn:solid-server:default:ContainerToTemplateConverter" },
            { "@id": "urn:solid-server:default:ErrorToQuadConverter" },
            { "@id": "urn:solid-server:default:ErrorToTemplateConverter" },
            { "@id": "urn:solid-server:default:MarkdownToHtmlConverter" }
          ]
        },
        {
          "@id": "urn:solid-server:default:ErrorToQuadConverter",
          "@type": "ErrorToQuadConverter",
        },
      ]
    }
  ]
}


<|start_filename|>configs/electron-main.js<|end_filename|>
const {app, BrowserWindow, BrowserView, Menu} = require('electron');
const fs = require('fs/promises')
const path = require('path')
const electronReload = require('electron-reload')
const jsonfile = require('jsonfile');
const {getUserConfig,mungeMenu} = require('./electron-config.js');
let mainWindow;
var cfg,css;

let installDir = path.join(__dirname,"../");
let tmplDir = path.join(installDir,"templates");
let assetsDir = path.join(installDir,"assets");

// during development, live reload on change of any file
electronReload( installDir,{forceHardReset:true} );

async function getConfig(){
  let kFrom = path.join(tmplDir,'kitchen.json.tmpl')
  let kTo = path.join(installDir,'kitchen.json')
  try {
    await fs.copyFile( kFrom, kTo );
  }
  catch(e){
    console.log("Could not create kitchen.js from "+js,e);
    process.exit();
  }
  cfg = await getUserConfig( path.join(installDir,"kitchen.json") ) || {};
  cfg.port ||= 3000;
  cfg.startPage = `http://localhost:${cfg.port}/`;
  cfg.icon = path.join(installDir,"assets/favicon.ico");
  cfg.webPreferences = {
    preload: path.join(installDir, 'configs/electron-preload.js'),
    nativeWindowOpen: true,
    nodeIntegration: false,
    contextIsolation: true,
  }
  return cfg;
}
getConfig().then( async (cfg)=>{
  await prepRootFilePath(cfg);
  spawnCSS(cfg);
});

async function prepRootFilePath(cfg){
  let root = cfg.rootFilePath;
  let port = cfg.port;
  let rootProfile = path.join(root,"profile") ;

  let aclFrom = path.join(tmplDir,"acl.tmpl");
  let aclTo   = path.join(root,".acl");
  let metaFrom = path.join(tmplDir,"meta.tmpl");
  let metaTo = path.join(root,".meta");
  let jsFrom = path.join(tmplDir,'kitchen.js.tmpl')
  let pFrom = path.join(tmplDir,'profile')
  let jsTo = path.join(assetsDir,'kitchen.js')
  let profileFrom = path.join(tmplDir,"profile/card$.ttl");

  let settingsFrom = path.join(pFrom,"settings") ;
  let settingsTo = path.join(rootProfile,"settings") ;

  // copy munged kitchen.js templates to assets
  try {
    let jsString = await fs.readFile( jsFrom,{encoding:'utf8'} );
    jsString =  "var port ="+cfg.port+";\n"+jsString ;
    await fs.writeFile( jsTo, jsString );
  }
  catch(e){
    console.log("Could not create kitchen.js from "+js,e);
    process.exit();
  }
  // copy acl & meta templates to rootFilePath
  // and kitchen.json to installDir
  try {
    await fs.copyFile( aclFrom, aclTo );
    await fs.copyFile( metaFrom, metaTo );
    await fs.mkdir( rootProfile );
  }
  catch(e){}
  try {
    await fs.mkdir( settingsTo );
  }
  catch(e){}
  try {
    if( await fs.access(path.join(rootProfile,"card$.ttl")) ) {}
  }
  catch(e){
      await fs.copyFile( profileFrom, rootProfile+"/card$.ttl" );
  }
  try {
    if( await fs.access( path.join(settingsTo,"prefs.ttl") ) ) {}
  }
  catch(e){
      await fs.copyFile( path.join(settingsFrom,'prefs.ttl'), path.join(settingsTo,"prefs.ttl" ) );
      await fs.copyFile( path.join(settingsFrom,'privateTypeIndex.ttl'), path.join(settingsTo,"privateTypeIndex.ttl" ) );
      await fs.copyFile( path.join(settingsFrom,'publicTypeIndex.ttl'), path.join(settingsTo,"publicTypeIndex.ttl" ) );
  }
}


/* ELECTRON STUFF
*/
app.on('ready', async ()=>{
/*
  actions that would happen here, are, instead, triggered once CSS is loaded
*/
})
app.on('close', function (e) {
  closeAll();
})
app.on('will-quit', function (e) {
  closeAll();
})
process.on('uncaughtException', function (error) {
  closeAll();
});

function closeAll(){
  if (process.platform !== 'darwin') app.quit()
}
app.on('activate', async function () {
  if (mainWindow === null) createWindow(cfg);
})
async function createWindow (cfg,css) {
 console.clear();
 console.log(`\nServing Data-Kitchen from ${cfg.rootFilePath} using port ${cfg.port}.\n`)
  mainWindow = new BrowserWindow(cfg)
  Menu.setApplicationMenu(
    Menu.buildFromTemplate( mungeMenu(cfg,mainWindow) )
  )
  //  mainWindow.removeMenu(); // NO TOP MENU
  if(cfg.devTools==1) mainWindow.webContents.openDevTools() /* DEV-TOOLS */
  mainWindow.loadURL(cfg.startPage)
/*
  mainwindow.webContents.session.clearStorageData([], function (data) {
    console.log(data);
  })
*/
  mainWindow.on('closed', function () { mainWindow = null })
}

/* START CSS IN A SEPARATE PROCESS, THEN START ELECTRON 
   -- user starts both with npm start and doesn't need to close CSS separately
*/
function spawnCSS(cfg){
  const cssStartPath = path.join(__dirname,"css-start.sh");
  var serverStarted=false;
  const { spawn } = require("child_process");
  css = spawn(cssStartPath,[cfg.rootFilePath,cfg.port]);
  css.stdout.on("data", data => {
    if( !serverStarted && data.toString().match(/Starting server/) ){
      serverStarted = true;
      createWindow(cfg,css);
    }
    console.log(`stdout: ${data}`);
  });
  css.stderr.on("data", data => {
    console.log(`stderr: ${data}`);
  });
  css.on('error', (error) => {
    //    console.log(`error: ${error.message}`);
    css.stdin.pause();
    css.kill();
  });
  css.on("close", code => {
    css.stdin.pause();
    css.kill();
  //    console.log(`error: ${code}`);
  });
}
/* END OF CSS STUFF */


// ENDS!


<|start_filename|>configs/electron-preload.js<|end_filename|>
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld(
  'ipcRenderer',
  {
    init: (go) => {
      ipcRenderer.on('gotoSubject',(event,uri)=>{
        go(uri);
      });
    }
  }
)


<|start_filename|>configs/electron-config.js<|end_filename|>
const jsonfile = require('jsonfile');
const path = require('path')

module.exports.getUserConfig = async function(configFile){
  try{ 
    cfg = await jsonfile.readFileSync( configFile ) 
  }
  catch(e){
    console.log("Could not load config file kitchen.json: ",e);
    process.exit();
  }
  cfg = cfg || {}
  cfg.width  ||=  1024
  cfg.height ||=  768
  if(cfg.left === "center") cfg.left = null;
  if(cfg.top === "center") cfg.top = null;
  cfg.rootFilePath = cfg.rootFilePath || "../";
  cfg.port = cfg.port || 3000;
  let parent = path.join(__dirname,"../");
  if(!cfg.rootFilePath.startsWith("/")) cfg.rootFilePath = path.join(parent,cfg.rootFilePath);
  
  return cfg;
}

module.exports.mungeMenu = function(cfg,mainWindow){
  let newBM = []
  if( cfg.bookmarks ){
    for(var b=0;b<cfg.bookmarks.length;b++){
      let url = cfg.bookmarks[b].url
      newBM.push({
        label : cfg.bookmarks[b].label,
        click : async () => {
          mainWindow.webContents.send( 'gotoSubject', url )
        }
      })
    }
  }
  let myMenu = getMenu(cfg,mainWindow)
  for(var i=0;i<myMenu.length;i++){
    if(myMenu[i].label==="Bookmarks"){
      myMenu[i].submenu=newBM
    }
  }
  return myMenu
}
function getMenu(cfg,mainWindow) {
  const isMac = process.platform === 'darwin'
  return [
  // { role: 'dataBrowserMenu' }
  {
    label: 'The Kitchen',
    submenu: [
      { label: 'DataBrowser',
        click: async () => { mainWindow.webContents.send( 
          'gotoSubject',cfg.startPage
        )}
      },
      { label: 'File Manager',
        enabled : false,
        click: async () => {
          mainWindow.webContents.send(
            'gotoSubject', 'none', 'fileManager'
          )
        }
      },
      { label: 'SPARQL Query',
        enabled : false,
        click: async () => {
          mainWindow.webContents.send(
            'gotoSubject','none','queryForm'
          )
        }
      },
      { label:'Exit',
         click() { closeAll() } 
      },
    ]
  },
  // { role: 'toolsMenu' }
  {
    label: 'Tools',
    submenu: [
     { label: 'Reload Data Kitchen',
        accelerator: "CmdOrCtrl+R", 
        click() {
          mainWindow.reload();
          mainWindow.loadURL(
            cfg.startPage
          )
        } 
      },
      { label: 'Clear Cache',
        click: async () => {
          // alert('clearing cache ...')
          mainWindow.webContents.session.clearCache(() => {
            // alert('cache is cleared')
            mainWindow.webContents.session.clearStorageData()
          });
        }
      },
      { label: 'Install Kitchen Updates',
        enabled : false,
        click: async () => {
          mainWindow.webContents.send(
            'https://jeff-zucker.github.io/data-kitchen.html'
          )
        }
      },
      { role: 'toggledevtools' },
    ]
  },
  // { role: 'bookmarksMenu' }
  {
    label: 'Bookmarks',
    submenu: [
      { label: 'My local files',
        click: async () => { mainWindow.loadURL( cfg.startPage ) }
      },
      { label: 'My solid.community pod',
        click: async () => { mainWindow.loadURL(
          'https://jeff-zucker.solidcommunity.net/public/'
        )}
      },
      { label: 'My inrupt.net pod',
        click: async () => { mainWindow.loadURL(
          'https://pod.inrupt.com/jeff-zucker/public/'
        )}
      },
     ]
  },
  // { role: 'help' }
  {
    label: "Help",
    submenu: [
      {
        label: 'Welcome to the Kitchen',
        enabled : false,
        click: async () => {
          mainWindow.webContents.send(
            'gotoSubject', 'assets/welcome.html'
          )
        }
      },
      {
        enabled : false,
        label: 'DataBrowser User Guide',
        click: async () => {
          mainWindow.webContents.send(
            'gotoSubject','https://github.com/solid/userguide', 'webBrowser' )
        }
      },
      {
        label: 'Report Issues on this fork of the kitchen',
        enabled : false,
        click: async () => {
          mainWindow.webContents.send(
            'gotoSubject','https://github.com/jeff-zucker/data-kitchen/issues', 'webBrowser' )
        }
      },
      {
        label: 'About Solid',
        submenu: [
          { label: 'Solid : solidproject.org',
        enabled : false,
            click: async () => {
              mainWindow.webContents.send(
                'gotoSubject',
                'https://solidproject.org/',
                'webBrowser'      
              )
            }
          },
          { label: 'This Week in Solid',
        enabled : false,
            click: async () => {
              mainWindow.webContents.send(
                'gotoSubject',
                'https://solidproject.org/this-week-in-solid',
                'webBrowser'      
              )
            }
          },
          { label: 'Solid Gitter Chat',
        enabled : false,
            click: async () => {
              mainWindow.webContents.send(
                'gotoSubject',
                'https://gitter.im/solid/chat',
                'webBrowser'      
              )
            }
          },
          { label: 'Solid Forum',
        enabled : false,
            click: async () => {
              mainWindow.webContents.send(
                'gotoSubject',
                'https://forum.solidproject.org/latest',
                'webBrowser'      
              )
            }
          },
        ]
      } 
    ]
  }
]
}


<|start_filename|>assets/kitchen.css<|end_filename|>
  #inputArea {
    left:0px;
    width:100%;
    padding:0.5em;
    padding-top:1.5em;
    background-color:#d0d0d0;
  }
  #loginButtonArea {
    display:inline-block;
    margin-left:3em;
    padding:0 !important;
    margin:0 !important;
  }
  #loginButtonArea input {
    display:inline-block;
    margin:0.25em !important;
    padding:0.25em !important;
  }
  #webId {
    display:inline-block;
    padding-top:0.6em;
    padding-bottom:0.6em;
  }
