diff --git a/js/costcalc_export.jsx b/js/costcalc_export.jsx index 31b33f0..17eed25 100644 --- a/js/costcalc_export.jsx +++ b/js/costcalc_export.jsx @@ -1,403 +1,404 @@ -'use strict'; +'use strict' // This code manages the export part of the engine class ManageExport extends React.Component { constructor (props) { super(props) this.make_export = this.make_export.bind(this) this.make_copy = this.make_copy.bind(this) this.rmvempty = this.rmvempty.bind(this) this.hide = this.hide.bind(this) this.colsdef = ['Category', 'Provider', 'Name', 'Comments', 'Options', 'Cost'] this.colconv = 'Cost' this.state = { output: '', disp: false, rmvempty: false, typexp: '', ConvEnable: this.props.conv.Enable } this.cols = this.colsdef.slice(0) this.colsconv = this.cols.slice(0) this.colsconv.push(this.colconv) } render () { if (this.props.conv.Enable) { this.cols = this.colsconv } else { this.cols = this.colsdef } let opt = '' let output = '' if (this.state.disp) { opt = this.options_btn() output = this.make_output(this.props.data.data) } return (
{this.export_btn()}
{output}
{opt}
) } export_btn () { return (
) } options_btn (opt) { let Namermv = '' if (this.state.rmvempty) { Namermv = 'Display Empty Lines' } else { Namermv = 'Remove Empty Lines' } return (
) } rmvempty () { this.setState({ rmvempty: !this.state.rmvempty }) // this.make_export(this.typexp) } hide () { this.setState({ disp: false }) } make_copy () { this.fnSelect('export-output') document.execCommand('copy') this.fnDeSelect() alert('Copied') Stats.RecordEvent('Export', 'clipboard', 0) } make_output (rdata) { const data = this.read_export(rdata, this.state.typexp) const hcol = this.cols // Generate a stat export Stats.RecordEvent('Export', this.state.typexp, 0) switch (this.state.typexp) { case 'html': return this.htmlout(hcol, data) case 'htmlsrc': return this.htmlsrcout(hcol, data) case 'mark': return this.markout(hcol, data) case 'csv': return this.csvout(hcol, data) } } make_export (typ) { this.setState({ typexp: typ }) this.setState({ disp: true }) } htmlout (hcol, data) { let disps = '' if (projectduration > 1) disps = 's' let movecol = 3 let Convcol = null if (this.props.conv.Enable) { movecol = 4 Convcol = {ConvCurrency(this.props.data.total)} } return (
{this.makecol(hcol, 'html', true)} {this.htmltable(data)} {Convcol}
{projectname} {projectduration} year{disps} Total Cost {this.props.data.total}
) } htmlsrcout (hcol, data) { let disps = '' if (projectduration > 1) disps = 's' let movecol = 3 let Convcol = null if (this.props.conv.Enable) { movecol = 4 Convcol = <td align="center"> <strong>{ConvCurrency(this.props.data.total)}</strong></td> } return (

                 <table>
<thead>
{this.makecol(hcol, 'htmlsrc', true)} </thead>
<tbody>
{this.htmlsrctable(data)} <tr >
<td>{projectname}</td> <td>{projectduration} year{disps}</td> <td colSpan={hcol.length - movecol} align="right"><strong>Total Cost</strong></td><td align="center"><strong> {this.props.data.total}</strong></td> {Convcol}
</tr>
</tbody>
</table>
) } markout (hcol, data) { let disps = '' if (projectduration > 1) disps = 's' const Head = Array.from({ length: this.cols.length }, (v, k) => '---') let movecol = 3 let Convcol = null if (this.props.conv.Enable) { movecol = 4 Convcol = {ConvCurrency(this.props.data.total)}| } const col = Array.from({ length: hcol.length - movecol }, (v, k) => '| ') return (

                 |{this.makecol(hcol, 'mark', true)}
|{this.makecol(Head, 'mark')}
{this.marktable(data)} |{projectname}|{projectduration} year{disps}{col} Total Cost |{this.props.data.total}|{Convcol}
) } csvout (hcol, data) { let disps = '' if (projectduration > 1) disps = 's' let movecol = 3 let Convcol = null if (this.props.conv.Enable) { movecol = 4 Convcol = {ConvCurrency(this.props.data.total)}, } const col = Array.from({ length: hcol.length - movecol }, (v, k) => ',') return (

                 {this.makecol(hcol, 'csv', true)}
{this.csvtable(data)} {projectname},{projectduration} year{disps}{col} Total Cost ,{this.props.data.total},{Convcol}
) } makecol (cols, style, head) { const children = [] switch (style) { case 'html': for (let j = 0; j < cols.length; j++) { if (head) { children.push({cols[j]}) } else { children.push({cols[j]}) } } return children case 'htmlsrc': for (let j = 0; j < cols.length; j++) { if (head) { children.push(<th> {cols[j]} </th>) } else { children.push(<td> {cols[j]} </td>) } } return children case 'mark': for (let j = 0; j < cols.length; j++) { children.push( {cols[j]}|) } return children case 'csv': for (let j = 0; j < cols.length; j++) { children.push( {cols[j]},) } return children } } htmltable (data) { const items = [] for (let i = 0; i < data.length; i++) { const row = Object.values(data[i]) const children = this.makecol(row, 'html') items.push({children}) } return items } htmlsrctable (data) { const items = [] for (let i = 0; i < data.length; i++) { const row = Object.values(data[i]) const children = this.makecol(row, 'htmlsrc') items.push(<tr> {children} </tr>
) } return items } marktable (data) { const items = [] for (let i = 0; i < data.length; i++) { const row = Object.values(data[i]) const children = this.makecol(row, 'mark') items.push(|{children}
) } return items } csvtable (data) { const items = [] for (let i = 0; i < data.length; i++) { const row = Object.values(data[i]) const children = this.makecol(row, 'csv') items.push({children}
) } return items } read_export (rawexport, n) { const output = [] for (let cat = 0; cat < rawexport.length; cat++) { const state = rawexport[cat] for (let mod = 0; mod < state.length; mod++) { if ((!this.state.rmvempty) || (state[mod].Provider !== '')) { let tmp = {} tmp = { Category: state[mod].Category, Provider: state[mod].Provider, Name: state[mod].Name, Comments: state[mod].Comments, Options: this.read_options(state[mod].ExportCmp), Cost: state[mod].Cost } if (this.props.conv.Enable) tmp.Cost2 = ConvCurrency(state[mod].Cost) output.push(tmp) } } } return output } read_options (Options) { const output = [] for (let i = 0; i < Options.length; i++) { switch (this.state.typexp) { case 'html': output.push({Options[i].Name} : {Options[i].Value} ) break case 'htmlsrc': output.push(<b>{Options[i].Name}</b> : <i>{Options[i].Value}</i> ) break case 'mark': output.push(__{Options[i].Name}__ : *{Options[i].Value}* ) break case 'csv': output.push({Options[i].Name} : {Options[i].Value} ) break } if (i < Options.length - 1) { output.push(
) } } return output } fnSelect (objId) { this.fnDeSelect() + let range if (document.selection) { - var range = document.body.createTextRange() + range = document.body.createTextRange() range.moveToElementText(document.getElementById(objId)) range.select() } else if (window.getSelection) { - var range = document.createRange() + range = document.createRange() range.selectNode(document.getElementById(objId)) window.getSelection().addRange(range) } } fnDeSelect () { if (document.selection) { document.selection.empty() } else if (window.getSelection) { window.getSelection().removeAllRanges() } } } diff --git a/js/costcalc_main.jsx b/js/costcalc_main.jsx index 50098ff..77f25b3 100644 --- a/js/costcalc_main.jsx +++ b/js/costcalc_main.jsx @@ -1,1703 +1,1703 @@ 'use strict' let projectname = '' let projectduration = 0 // Functions Tools // --------------------- // --------------------- // function loop for react js function Repeat (props) { const items = [] for (let i = 0; i < props.numTimes; i++) { items.push(props.children(i)) } return
{items}
} // convert string to numeric function toNumeric (value) { return parseFloat( value.toString().replace(/[^0-9\.]+/g, '') ) } // Covert numeric to money string function toMoney (numeric, currency) { if (typeof numeric === 'string') { numeric = parseFloat(numeric) } let strcur = '' if (currency === undefined) { strcur = MainData.Currency } else { strcur = currency } return numeric.toFixed(0).replace(/(\d)(?=(\d{3})+\.)/g, '$1,') + ' ' + strcur } // return the sum of an array function sum (obj) { const val = Object.values(obj) let total = 0 for (let i = 0; i < val.length; i++) { total = total + toNumeric(val[i]) } return total } // Comapare two obj return true is similar Object.compare = function (obj1, obj2) { // Loop through properties in object 1 for (const p in obj1) { // Check property exists on both objects if (obj1.hasOwnProperty(p) !== obj2.hasOwnProperty(p)) return false switch (typeof (obj1[p])) { // Deep compare objects case 'object': if (!Object.compare(obj1[p], obj2[p])) return false break // Compare function code case 'function': - if (typeof (obj2[p]) === 'undefined' || (p != 'compare' && obj1[p].toString() != obj2[p].toString())) return false + if (typeof (obj2[p]) === 'undefined' || (p !== 'compare' && obj1[p].toString() !== obj2[p].toString())) return false break // Compare values default: - if (obj1[p] != obj2[p]) return false + if (obj1[p] !== obj2[p]) return false } } // Check object 2 for any extra properties for (const p in obj2) { if (typeof (obj1[p]) === 'undefined') return false } return true } // Generate a random int function randomInt (not) { let rnd let cont do { rnd = Math.floor(Math.random() * 100) cont = false for (let i = 0; i < not.length; i++) { if (not[i] === rnd) { cont = true } } } while (cont) return rnd } // Inputs Definition // --------------------- // --------------------- // Display the amount selector class AmountInput extends React.Component { constructor (props) { super(props) this.handleChange = this.handleChange.bind(this) } handleChange (e) { this.props.onChange(e.target.value) } componentDidMount () { $('[data-toggle="tooltip"]').tooltip() } componentDidUpdate () { $('[data-toggle="tooltip"]').tooltip() } componentWillUnmount () { $('[data-toggle="tooltip"]').tooltip('dispose') } render () { const value = this.props.value let label = null if (this.props.name != null && this.props.name !== '') { label = } return (
{label} {this.props.name} : {value} {this.props.unit}
) } } // Display a select input box class SelectorInput extends React.Component { constructor (props) { super(props) // this.state={listoptions:this.makelist(props.options)}; this.handleChange = this.handleChange.bind(this) } rate (i) { return i } makelist (data) { const listoptions = [] for (let i = 0; i < data.length; i++) { listoptions.push() } return listoptions } handleChange (select) { this.props.onChange(select.target.value) } makerate () { if (this.props.rate != null && this.props.rate !== '') { return (
Rate : {this.props.rate} {this.props.unit}
) } } maketitle (title) { const maxstr = 20 if (title.length > maxstr) { title = title.substr(0, maxstr) + '...' } return title } componentDidMount () { $('[data-toggle="tooltip"]').tooltip() } componentDidUpdate () { $('[data-toggle="tooltip"]').tooltip() } componentWillUnmount () { $('[data-toggle="tooltip"]').tooltip('dispose') } render () { let label = null if (this.props.name != null && this.props.name !== '') { label =
} return (
{label}
{this.makelist(this.props.options)}
{this.makerate()}
) } } // Make the read more button class MakeknowmoreInput extends React.Component { constructor (props) { super(props) this.state = { btnsize: 20 } } componentDidMount () { $('[data-toggle="tooltip"]').tooltip() } componentDidUpdate () { $('[data-toggle="tooltip"]').tooltip() } componentWillUnmount () { $('[data-toggle="tooltip"]').tooltip('dispose') } render () { const data = this.props.data if (((data.Url !== ''))) { // if (data.Url.length==1){ // return( // } url={data.Url[0].Url} // id="btn-plugin-knowmore" // class="btn-primary btn-sm" tips={"Know more about " + data.Name}/> // ); // }else { return (} options={data.Url} id="btn-plugin-knowmore" class="btn-primary btn-sm" tips={'Read more about ' + data.Name + ' solutions'}/>) // } } else { return null } } } // Display a checkbox class CheckboxInput extends React.Component { constructor (props) { super(props) this.handleChange = this.handleChange.bind(this) this.state = { checked: this.props.defaults } } handleChange () { this.setState({ checked: !this.state.checked }) this.props.onChange(!this.state.checked) } render () { return (
) } } // Display a btn with link class ButtonHrefInput extends React.Component { constructor (props) { super(props) } componentDidMount () { $('[data-toggle="tooltip"]').tooltip() } componentDidUpdate () { $('[data-toggle="tooltip"]').tooltip() } render () { return ( {this.props.name} ) } } // Button with validation popup class ButtonInputWpop extends React.Component { constructor (props) { super(props) this.handleChange = this.handleChange.bind(this) this.state = { target: 'Modal' + this.props.idp } } handleChange () { const out = { n: this.props.n, target: this.state.target } this.props.onClick(out) } componentDidMount () { $('[data-toggle="tooltip"]').tooltip() } componentDidUpdate () { $('[data-toggle="tooltip"]').tooltip() } componentWillUnmount () { $('[data-toggle="tooltip"]').tooltip('dispose') } render () { return ( ) } } // Display a button class ButtonInput extends React.Component { constructor (props) { super(props) this.handleChange = this.handleChange.bind(this) } handleChange () { this.props.onClick(this.props.n) } componentDidMount () { $('[data-toggle="tooltip"]').tooltip() } componentDidUpdate () { $('[data-toggle="tooltip"]').tooltip() } componentWillUnmount () { $('[data-toggle="tooltip"]').tooltip('dispose') } render () { return ( ) } } // Display a menu class MenuInput extends React.Component { constructor (props) { super(props) this.state = { listoptions: this.makelist(props.options) } } componentDidMount () { $('[data-toggle="tooltip"]').tooltip() } componentDidUpdate () { $('[data-toggle="tooltip"]').tooltip() } componentWillUnmount () { $('[data-toggle="tooltip"]').tooltip('dispose') } makelist (data) { const listoptions = [] for (let i = 0; i < data.length; i++) { listoptions.push({data[i].Name}) } return listoptions } render () { return (
{this.state.listoptions}
) } } // Text input box class TxtInput extends React.Component { constructor (props) { super(props) this.handleChange = this.handleChange.bind(this) } handleChange (e) { this.props.onChange(e.target.value) } componentDidMount () { $('[data-toggle="tooltip"]').tooltip() } componentDidUpdate () { $('[data-toggle="tooltip"]').tooltip() } componentWillUnmount () { $('[data-toggle="tooltip"]').tooltip('dispose') } render () { let info = null if (this.props.info != null && this.props.info !== '') info = {this.props.info} return (
{this.props.Prepend}
{info}
{this.props.InvalidMessage}
) } } // Outputs definition // --------------------- // --------------------- // Display the cost output box class CostOutput extends React.Component { constructor (props) { super(props) this.handleChange = this.handleChange.bind(this) } handleChange () { this.props.onCostChange(this.props.display) } componentDidMount () { $('[data-toggle="tooltip"]').tooltip() } componentDidUpdate () { $('[data-toggle="tooltip"]').tooltip() } render () { const classN = 'form-control ' + this.props.class return (
) } } // Display a text box for display function Textoutput (props) { return (
{props.text}
) } // Plugins definition // --------------------- // --------------------- class AmountRatesCost extends React.Component { constructor (props) { super(props) this.handleAmountChange = this.handleAmountChange.bind(this) this.handleRateChange = this.handleRateChange.bind(this) this.state = { Amount: 1, SelectRate: 0, Rate: this.props.data.Rates[Object.keys(this.props.data.Rates)[0]], Adaptive: false } if (typeof this.props.data.Adaptive !== 'undefined' && this.props.data.Adaptive === true) { this.state.Adaptive = true } this.makeExport() } handleAmountChange (amount) { this.setState({ Amount: amount }) } handleRateChange (select) { this.setState({ SelectRate: select }) this.setState({ Rate: this.props.data.Rates[Object.keys(this.props.data.Rates)[select]] }) } makeExport () { this.export = [ { Name: 'Amount', Value: this.state.Amount + ' ' + this.props.data.AmountUnit }, { Name: this.props.data.RateName, Value: Object.keys(this.props.data.Rates)[this.state.SelectRate] } ] this.props.export(this.export) } componentDidUpdate () { this.makeCost(this.state.Amount, this.state.Rate) this.makeExport() } render () { let AmountMin let AmountMax let AmountStep if (this.state.Adaptive) { AmountMin = this.props.data.AmountMin[this.state.SelectRate] AmountMax = this.props.data.AmountMax[this.state.SelectRate] AmountStep = this.props.data.AmountStep[this.state.SelectRate] } else { AmountMin = this.props.data.AmountMin AmountMax = this.props.data.AmountMax AmountStep = this.props.data.AmountStep } if (this.state.Amount > AmountMax) { this.setState({ Amount: AmountMax }) } if (this.state.Amount < AmountMin) { this.setState({ Amount: AmountMin }) } return (
) } makeCost (amount, rate) { let free if (this.state.Adaptive) { free = this.props.data.AmountFree[this.state.SelectRate] } else { free = this.props.data.AmountFree } let total = (amount - free) * rate if (this.props.data.ByYear) total = total * projectduration total = toMoney(total) this.props.onCostChange(this.props.n, total) return total } } class CategoryAmountRatesCost extends React.Component { constructor (props) { super(props) this.handleCatChange = this.handleCatChange.bind(this) this.handleAmountChange = this.handleAmountChange.bind(this) this.handleRateChange = this.handleRateChange.bind(this) // console.log('in constructor', this.props.data.Rates) this.state = { SelectCat: 0, Cat: this.props.data.Cat[Object.keys(this.props.data.Cat)[0]], Amount: 0, SelectRate: 0, Rate: this.props.data.Rates[Object.keys(this.props.data.Rates)[0]], Free: 0, Adaptive: false } if (typeof this.props.data.Adaptive !== 'undefined' && this.props.data.Adaptive === true) { this.state.Adaptive = true } this.makeExport() } handleAmountChange (amount) { this.setState({ Amount: amount }) } handleRateChange (select) { this.setState({ SelectRate: select }) this.setState({ Rate: this.props.data.Rates[Object.keys(this.props.data.Rates)[select]] }) } handleCatChange (select) { this.setState({ SelectCat: select }) this.setState({ Cat: this.props.data.Cat[Object.keys(this.props.data.Cat)[select]] }) } makeExport () { this.export = [ { Name: this.props.data.CatName, Value: Object.keys(this.props.data.Cat)[this.state.SelectCat] }, { Name: 'Amount', Value: this.state.Amount + ' ' + this.props.data.AmountUnit }, { Name: this.props.data.RateName, Value: Object.keys(this.props.data.Rates)[this.state.SelectRate] } ] this.props.export(this.export) } componentDidUpdate () { this.makeCost(this.state.Cat, this.state.Amount, this.state.Rate, this.state.Free) this.makeExport() } render () { let AmountMin let AmountMax let AmountStep let AmountFree if (this.state.Adaptive) { // console.log('this.props.data.AmountMin', this.props.data.AmountMin, 'this.props.data.AmountMax', this.props.data.AmountMax, ' selected rate', this.state.SelectRate) AmountMin = this.props.data.AmountMin[this.state.SelectRate] AmountMax = this.props.data.AmountMax[this.state.SelectRate] AmountStep = this.props.data.AmountStep[this.state.SelectRate] AmountFree = this.props.data.AmountFree[this.state.SelectRate] } else { AmountMin = this.props.data.AmountMin AmountMax = this.props.data.AmountMax AmountStep = this.props.data.AmountStep AmountFree = this.props.data.AmountFree } if (this.state.Amount > AmountMax) { this.setState({ Amount: AmountMax }) } if (this.state.Amount < AmountMin) { this.setState({ Amount: AmountMin }) } // console.log('in render()', this.state.SelectRate, AmountMin, AmountMax, AmountStep, AmountFree) return (
) } makeCost (cat, amount, rate, free) { let total = cat + (amount - free) * rate // console.log(cat, '+ (', amount, '-',free,')*', rate, ' = ', total) if (this.props.data.ByYear) total = total * projectduration total = toMoney(total) this.props.onCostChange(this.props.n, total) return total } } class CategoryCost extends React.Component { constructor (props) { super(props) this.handleCatChange = this.handleCatChange.bind(this) this.state = { SelectCat: 0, Cat: this.props.data.Cat[Object.keys(this.props.data.Cat)[0]] } this.makeExport() } handleCatChange (select) { this.setState({ SelectCat: select }) this.setState({ Cat: this.props.data.Cat[Object.keys(this.props.data.Cat)[select]] }) } makeExport () { this.export = [ { Name: this.props.data.CatName, Value: Object.keys(this.props.data.Cat)[this.state.SelectCat] } ] this.props.export(this.export) } componentDidUpdate () { this.makeCost(this.state.Cat) this.makeExport() } render () { return (
) } makeCost (cat) { let total = cat if (this.props.data.ByYear) total = total * projectduration total = toMoney(total) this.props.onCostChange(this.props.n, total) return total } } class NoneSelect extends React.Component { constructor (props) { super(props) this.export = [] } render () { const Cost = toMoney(0) this.props.onCostChange(this.props.n, Cost) this.props.export(this.export) return (
Please select a provider in the list.
) } } class UserCost extends React.Component { constructor (props) { super(props) this.handleCostChange = this.handleCostChange.bind(this) this.handleProviderChange = this.handleProviderChange.bind(this) this.handleServiceChange = this.handleServiceChange.bind(this) this.handleYearChange = this.handleYearChange.bind(this) this.handleConvMoneyChange = this.handleConvMoneyChange.bind(this) this.state = { total: 0, value: 0, ProviderError: true, ServiceError: true, ByYear: false } this.export = [] } handleYearChange (state) { this.setState({ ByYear: state }) this.props.handlebyYearChange(state) } makeCost (byYear, amount) { let total = amount if (byYear) total = amount * projectduration // this.setState({total:total}); this.props.onCostChange(this.props.n, toMoney(total)) } handleCostChange (value) { this.setState({ value }) } handleProviderChange (txt) { this.props.handleProviderChange(txt) if (txt === '') { this.setState({ ProviderError: true }) } else { this.setState({ ProviderError: false }) } } handleServiceChange (txt) { this.props.handleServiceChange(txt) if (txt === '') { this.setState({ ServiceError: true }) } else { this.setState({ ServiceError: false }) } } handleConvMoneyChange (conv) { this.setState({ conv }) } componentDidUpdate () { this.makeCost(this.state.ByYear, this.state.value) // this.makeExport(); } classtxt (error) { if (error) { return 'is-invalid' } else { return 'is-valid' } } render () { let Costname = 'Cost' if (this.state.ByYear) Costname = 'Cost by year' this.props.export(this.export) return (
) } } // Combine plugins // --------------------- // --------------------- // Manages what's display inside a plugin : provider selector, select the components... class ProviderPluginsSelector extends React.Component { constructor (props) { super(props) this.handleCostChange = this.handleCostChange.bind(this) this.handleProviderChange = this.handleProviderChange.bind(this) this.handleCommentChange = this.handleCommentChange.bind(this) this.handleAddPlugin = this.handleAddPlugin.bind(this) this.handleRmvPlugin = this.handleRmvPlugin.bind(this) this.handleProviderChangetxt = this.handleProviderChangetxt.bind(this) this.handleServiceChangetxt = this.handleServiceChangetxt.bind(this) this.handlebyYearChange = this.handlebyYearChange.bind(this) this.makeExportcmp = this.makeExportcmp.bind(this) this.makeExport = this.makeExport.bind(this) this.state = { selected: 0, keys: this.ProvidersName(props.data), n: 1, cost: 0, comments: '', Provider: '', Name: '', manualname: false, manbyyear: false, showPlus: false, exportcmp: '' } } handleCostChange (n, e) { if (this.state.cost !== e) { this.setState({ cost: e }) this.props.handleCostChange(n, e) } } handleProviderChange (select) { this.setState({ selected: select }) if (select > 0) { this.setState({ showPlus: true }) } else { this.setState({ showPlus: false }) } this.setState({ Provider: this.props.data.Data[select].Provider }) this.setState({ Name: this.props.data.Data[select].Name }) this.props.handleCostChange(this.props.n, this.state.cost) // Send a provider even when provider change Stats.RecordEvent('Provider', this.state.Provider, 0) } componentDidUpdate () { this.props.handleCostChange(this.props.n, this.state.cost) this.makeExport() } makeExportcmp (data) { this.state.exportcmp = data } makeExport () { const out = { Category: this.props.data.Name, Provider: this.state.Provider, Name: this.state.Name, Comments: this.state.comments, ExportCmp: this.state.exportcmp, Cost: this.state.cost } this.props.export(out, this.props.n) } handleCommentChange (com) { this.setState({ comments: com }) } handleAddPlugin (n) { this.props.handleAddPlugin(n) } handleRmvPlugin (n) { this.props.handleRmvPlugin(n) } // The 3 nexts function are for user input management handleProviderChangetxt (txt) { this.setState({ Provider: txt }) } handleServiceChangetxt (txt) { this.setState({ Name: txt }) } handlebyYearChange (state) { this.setState({ manbyyear: state }) } // Manage extra display info for a selected provider extrainfo (Cdata) { let ExtraInf = '' let ExtraInfUrl = '' if (typeof Cdata.ExtraInfoUrl !== 'undefined' && Cdata.ExtraInfoUrl !== '') { ExtraInfUrl =

To know more

} if (typeof Cdata.ExtraInfo !== 'undefined' && Cdata.ExtraInfo !== '') { ExtraInf =
  {Cdata.ExtraInfo} {ExtraInfUrl}
} return (ExtraInf) } render () { const selected = this.state.selected this.state.manualname = false this.state.keys = this.ProvidersName(this.props.data) const Cmp = this.cmp2string(this.cmpdata(selected).Style) const Cdata = this.cmpdata(selected) const id = this.props.data.Name.replace(/\s/g, '') + this.props.n return (
{this.extrainfo(Cdata)}
) } cmpdata (select) { const out = this.props.data.Data[select] if (this.state.manualname) { out.Name = this.state.Name out.ByYear = this.state.manbyyear if (this.state.Provider === '') { this.state.keys[select] = 'Please provide a Provider' } else { this.state.keys[select] = this.state.Provider } } return out } // return the correct style fct from the str input cmp2string (str) { switch (str) { case 'AmountRatesCost' : return AmountRatesCost case 'CategoryCost' : return CategoryCost case 'CategoryAmountRatesCost' : return CategoryAmountRatesCost case 'NoneSelect' : return NoneSelect case 'UserCost' : { this.setState({ manualname: true }); return UserCost } } } ProvidersName (main) { const data = main.Data const providers = [] for (let i = 0; i < data.length; i++) { providers.push(data[i].Provider) } return providers } } // Displays the header of a plugin (button +- name cost ...) class ModuleHeader extends React.Component { constructor (props) { super(props) this.handleAddPlugin = this.handleAddPlugin.bind(this) this.handleRmvPlugin = this.handleRmvPlugin.bind(this) } handleAddPlugin (n) { this.props.handleAddPlugin(n) } handleRmvPlugin (n) { this.props.handleRmvPlugin(n) } byyear (by) { if (by) { return ({projectduration}
years
) } else { return () } } render () { let minus = null let plus = null let convout = null if (this.props.showMinus) { minus = } onClick={this.handleRmvPlugin} n={this.props.n} tips="Remove this line" idp={this.props.id} info={this.props.data.Name}/> } if (this.props.showPlus) { plus = } onClick={this.handleAddPlugin} n={this.props.n} tips={'Add a new ' + this.props.data.Name}/> } if (this.props.conv.Enable) { convout = } return (
{plus}
{minus}
{/*
*/} {/*
*/} {/*
*/} {/*
*/}
{this.makeinfo(this.props.keys, this.props.selected, this.props.Cdata)}
{this.props.comments}
{convout}
{this.byyear(this.props.Cdata.ByYear)}
) } btnClick () { // Send a category even when someone click on the category btn Stats.RecordEvent('Category', this.props.data.Name, this.props.n) } makeinfo (keys, selected, Cdata) { let name = Cdata.Name if (name === '' && keys[selected] === '') { name = 'Please provide a Provider' return ({name}) } else if (keys[selected] === 'None') { return ({name}) } else { return ({keys[selected]} : {name}) } } } // displays one kind plugin it manages the add and removes option class ManagePlugins extends React.Component { constructor (props) { super(props) this.handleCostChange = this.handleCostChange.bind(this) this.handleAddPlugin = this.handleAddPlugin.bind(this) this.handleRmvPlugin = this.handleRmvPlugin.bind(this) this.makeExportplug = this.makeExportplug.bind(this) this.makeExport = this.makeExport.bind(this) this.state = { displayed: [], varsum: {}, plugins: [], export: [] } this.state.displayed.push(randomInt(this.state.displayed)) } handleRmvPlugin (n) { $('#' + n.target).modal('hide') const tmp = this.state.displayed tmp.splice(n.n, 1) this.setState({ displayed: tmp }) this.handleCostChange(n.n, 0) this.state.export.splice(n.n, 1) } handleAddPlugin (n) { const tmp = this.state.displayed tmp.splice(n + 1, 0, randomInt(this.state.displayed)) this.setState({ displayed: tmp }) } handleCostChange (n, cost) { this.state.varsum[n] = cost this.props.handleCostChange(this.props.n, sum(this.state.varsum)) } makeExportplug (data, n) { this.state.export[n] = data this.makeExport() } makeExport () { if (this.state.export.length === this.giveN()) { this.props.export(this.state.export, this.props.n) } } giveId (index) { return this.state.displayed[index] } giveN () { const disp = this.state.displayed return disp.length } componentDidUpdate () { this.makeExport() } render () { let showMinus = false if (this.giveN() > 1) { showMinus = true } this.makeExport() return (
{(index) => }
) } } // displays all the plugins defined in the Maindata class PluginsMain extends React.Component { constructor (props) { super(props) this.handleCostChange = this.handleCostChange.bind(this) this.makeExportplug = this.makeExportplug.bind(this) this.makeExport = this.makeExport.bind(this) this.state = { varsum: {}, export: [] } } handleCostChange (name, e) { this.state.varsum[name] = e this.props.TotalCost(sum(this.state.varsum)) } makeExportplug (data, n) { this.state.export[n] = data this.makeExport() } makeExport () { if (this.state.export.length === this.props.data.length) { this.props.export(this.state.export) } } render () { return (
Line controls
Category
Provider information
Cost
{(index) => }
) } } // MAIN // --------------------- // --------------------- class Main extends React.Component { constructor (props) { MoneyGetRates() super(props) this.handleCostChange = this.handleCostChange.bind(this) this.makeExportmain = this.makeExportmain.bind(this) this.handleNameChange = this.handleNameChange.bind(this) this.handleDurationChange = this.handleDurationChange.bind(this) this.handleConvMoneyChange = this.handleConvMoneyChange.bind(this) this.btnClick = this.btnClick.bind(this) this.state = { total: 0, export: [], exportmain: [], name: '', duration: MainData.DefaultDuration, conv: { Enable: false, Cur: '' } } projectduration = this.state.duration this.init = true } componentDidUpdate () { _paq.push(['enableLinkTracking']) } handleCostChange (total) { if (this.state.total !== total) { this.setState({ total }) } } makeExportmain (idata) { const tmp = JSON.parse(JSON.stringify(idata)) let disp = false if (!this.init) { if (!Object.compare(tmp, this.state.exportmain.data)) { disp = true } } if ((this.init) || (disp)) { this.setState({ exportmain: { data: tmp, total: toMoney(this.state.total) } }) this.init = false } } handleNameChange (name) { this.setState({ name }) projectname = name } handleDurationChange (d) { this.setState({ duration: d }) projectduration = d } handleConvMoneyChange (conv) { this.setState({ conv }) } btnClick (name, value) { Stats.RecordEvent('Options', name, value) } render () { return (
{this.page_head()}
{this.project_info()} {this.final_cost(this.state.conv)} {this.howto()}
{this.page_foot()}
) } project_info () { return (
) } // Display the total cost final_cost (conv) { let disps = '' if (projectduration > 1) disps = 's' let convout = '' if (conv.Enable) convout = return (
{ /* */ }

Total Cost for {projectduration} year{disps}

{convout}
) } // Define the head (top) of the page page_head () { let helpbtn = null let imglogo = null if (MainData.HelpUrl != null && MainData.HelpUrl !== '') { helpbtn = this.btnClick('helpbtn', 0)} rel="noreferrer">  I need help with my DMP } if (MainData.InstLogo != null && MainData.InstLogo !== '') { imglogo = } return ( ) } // Define the foot (bottom) of the page page_foot () { return ( ) } // Define the howto (user guide) howto () { let curconv = null if (MoneyEnable) { curconv =
Change Currency

If you need another currency than {MainData.Currency} you can add an extra currency by selecting in the Change Currency menu

Actual rate is automatically applied using openexchangerates

} return (

HOWTO

Project Name and Duration

The Project name is only used for you.

Project Duration is used for subscription services charged by year : the yearly cost will be multiplied by the duration of the project.

{curconv}
Categories
This tool is divided by categories (for example Activate storage). Click on the category name, and it will expand.
Providers

Providers can be chosen from the Select a provider box. You can then tune your settings for this provider to fit your needs.

If the provider you want is not present, you can add it manually with Provide your own provider and then enter your provider/service and cost.

Add or Remove Line

If you want to add a new line use the } tips={'Add a new category'} onClick={this.fctnull}/> button.

You can also remove a line with } tips={'Remove this line'} onClick={this.fctnull}/> button.

To know more about
Some extra information about the category or the provider can be obtained with the } tips={'Know more'} onClick={this.fctnull}/> button.
Comments your input
Comments are for your own usage, you can use it for remembering what each section is and for a nice export.
Export
You can export your work into different formats :
HTML : This format can be used in any wordprocessing software (such as Microsoft Word or Libreoffice).
HTML Source code, Markdown, and CSV formats are also possible.
Click on the in order to copy your work into your clipboard. A simple Paste will transfer your work into any software.
) } // Function use by the howto btn to move the page move2howto () { Stats.RecordEvent('Options', 'howtobtn', 0) $('html,body').animate({ scrollTop: $('#howto').offset().top }, 'slow') } fctnull () {} } // Main Declaration // --------------------- // --------------------- ReactDOM.render(
, document.getElementById('root')) // Display the stats popup after 10s if (Stats.Enable) { setTimeout(function () { $('#PopupStats').modal('show') }, 10000) } // Enable tooltip $('[data-toggle="tooltip"]').tooltip() diff --git a/js/data.js b/js/data.js index c7e3b57..afb42c6 100644 --- a/js/data.js +++ b/js/data.js @@ -1,685 +1,684 @@ // Providers // ---------------------------------------------------- // ---------------------------------------------------- // Storage const NasEpfl = { Style: 'AmountRatesCost', Provider: 'EPFL-VPO-DSI', Name: 'NAS2023', Url: [ { Name: 'DSI-Website', Url: 'https://support.epfl.ch/help/epfl?id=epfl_service_status&service=49a363acdb34c700ef64731b8c96191f' } ], ExtraInfo: '', ByYear: true, Adaptive: false, AmountName: 'Amount', AmountUnit: 'TB', AmountMin: 0, AmountMax: 1, AmountStep: 1, AmountFree: 1, AmountFreeCumulative: false, RateName: 'Performance', Rates: { Standard: 80 }, RateUnit: 'CHF / TB' } const RcpEpfl = { Style: 'AmountRatesCost', Provider: 'EPFL-VPA-RCP', Name: 'Collaborative Storage – NAS-RCP', Url: [ { Name: 'Service description', Url: 'https://www.epfl.ch/research/facilities/rcp/collaborative-storage-nas-rcp-service-description/' } ], ExtraInfo: '', ByYear: true, Adaptive: false, AmountName: 'Amount', AmountUnit: 'TB', AmountMin: 0, AmountMax: 25, AmountStep: 1, AmountFree: 0, AmountFreeCumulative: false, RateName: 'Performance', Rates: { Standard: 27 }, RateUnit: 'CHF / TB' } const ScitasEpfl = { Style: 'AmountRatesCost', Provider: 'EPFL-SCITAS', Name: 'Collaborative Storage – NAS-RCP', Url: [ { Name: 'Service description', Url: 'https://scitas-doc.epfl.ch/accounts/prices/' } ], ExtraInfo: '', ByYear: true, Adaptive: false, AmountName: 'Amount', AmountUnit: 'TB', AmountMin: 0, AmountMax: 25, AmountStep: 1, AmountFree: 0, AmountFreeCumulative: false, RateName: 'Performance', Rates: { 'User fee 1 (for EPFL: direct costs only)': 25, 'User fee 2 (incl. infrastructure maintenance': 63, 'User fee 3 (incl. all direct and indirect costs': 93 }, RateUnit: 'CHF / TB' } const SwitchEpfl = { Style: 'CategoryCost', Provider: 'Switch', Name: 'Online Storage', ByYear: true, Url: [ { Name: 'Switch Website', Url: 'https://drive.switch.ch/' } ], CatName: 'Options', Cat: { 'Cloud Based max 50GB': 0 }, CatUnit: 'CHF' } const MsPersonalEpfl = { Style: 'CategoryCost', Provider: 'Microsoft OneDrive', Name: 'Microsoft M365 OneDrive', ByYear: true, Url: [ { Name: 'Service description', Url: 'https://support.epfl.ch/epfl?id=epfl_kb_article_view&sysparm_article=KB0017390&sys_kb_id=44ce400f97b729142f9976971153af0c#mcetoc_1h4lngro134' } ], CatName: 'Options', Cat: { 'Individual storage 1TB': 0 }, CatUnit: 'CHF' } const MsSharedEpfl = { Style: 'AmountRatesCost', Provider: 'Microsoft SharePoint', Name: 'Microsoft M365 SharePoint', Url: [ { Name: 'Service description', Url: 'https://support.epfl.ch/epfl?id=epfl_kb_article_view&sysparm_article=KB0017390&sys_kb_id=44ce400f97b729142f9976971153af0c#mcetoc_1h4lngro137' } ], ByYear: true, Adaptive: false, AmountName: 'Amount', AmountUnit: 'TB', AmountMin: 0, AmountMax: 25, AmountStep: 1, AmountFree: 1, RateName: 'Standard', Rates: { Standard: 0 }, RateUnit: 'CHF', AmountFreeCumulative: false } const GoogleDriveEdu = { Style: 'CategoryCost', Provider: 'Google Workspace', Name: 'Online Storage', ByYear: true, Adaptive: false, ExtraInfo: 'Google Storage is not recommended as the data are stored outside of Switzerland', ExtraInfoUrl: 'https://support.epfl.ch/epfl?id=epfl_service_status&service=b1c22728db34c700ef64731b8c9619ad', Url: [ { Name: 'Google Education Page', Url: 'https://edu.google.com/intl/en_ALL/' } ], AmountName: 'Amount', AmountUnit: 'GB', AmountMin: 0, AmountMax: 20, AmountStep: 1, AmountFree: 0, CatName: 'Options', Cat: { 'Individual 20GB': 0, 'Group 50GB': 0 }, CatUnit: 'CHF' } const DropboxPerso = { Style: 'CategoryCost', Provider: 'Dropbox Personal', Name: 'Online Storage', ByYear: true, ExtraInfo: 'Dropbox must not be used for confidential data as the data are stored outside of Switzerland', ExtraInfoUrl: 'https://support.epfl.ch/kb_view_customer.do?sysparm_article=KB0012882', Url: [ { Name: 'Dropbox', Url: 'https://www.dropbox.com/plans?trigger=nr' } ], CatName: 'Plan', Cat: { 'Personal Free 2Go': 0, 'Personal Plus 2TB': 107, 'Essentials (3TB)': 193 }, CatUnit: 'CHF' } const DropboxTeam = { Style: 'AmountRatesCost', Provider: 'Dropbox for Team', Name: 'Online Storage', ByYear: true, ExtraInfo: 'Dropbox must not be used for confidential data as the data are stored outside of Switzerland', ExtraInfoUrl: 'https://support.epfl.ch/kb_view_customer.do?sysparm_article=KB0012882', Url: [ { Name: 'Dropbox', Url: 'https://www.dropbox.com/plans?trigger=nr' } ], Adaptive: false, AmountName: 'Number of Users', AmountUnit: 'Users', AmountMin: 1, AmountMax: 500, AmountStep: 1, AmountFree: 0, AmountFreeCumulative: false, RateName: 'Plan', Rates: { 'Business (9TB)': 172, 'Business Plus (15TB)': 214 }, RateUnit: 'CHF / User' } // ELN const SLIMSEpfl = { Style: 'CategoryAmountRatesCost', Provider: 'EPFL-SV-IT', Name: 'SLIMS', ByYear: true, Url: [ { Name: 'SLIMS on SV-IT Website', Url: 'https://www.epfl.ch/schools/sv/it/374-2/applications/eln-lims/' }, { Name: 'Genohm (SLIMS Company)', Url: 'https://www.genohm.com/' } ], CatName: 'PI Status', Cat: { 'Full Professor': 3000, 'Associate Professor': 2000, 'Tenure Track Assistant Professor or Core Facility': 1000 }, CatUnit: 'CHF', Adaptive: false, AmountName: 'Storage', AmountUnit: 'TB', AmountMin: 1, AmountMax: 100, AmountStep: 1, AmountFree: 0, AmountFreeCumulative: false, RateName: 'ELN Storage', Rates: { 'Stored on EPFL Server': 45 }, RateUnit: 'CHF / TB' } const ELNEpfl = { Style: 'CategoryCost', Provider: 'ELN-EPFL', Name: 'ELN', ByYear: true, Url: [ { Name: 'ELN Website', Url: 'https://eln.epfl.ch/' } ], CatName: 'Options', Cat: { 'Free for EPFL community': 0 }, CatUnit: 'CHF' } const Rspace = { Style: 'CategoryCost', Provider: 'Rspace community', Name: 'Rspace', ByYear: true, Url: [ { Name: 'Rspace Website', Url: 'https://www.researchspace.com/' } ], CatName: 'Options', Cat: { 'Cloud Based unlimited storage and user': 0 }, CatUnit: 'CHF' } const Benchling = { Style: 'CategoryCost', Provider: 'Benchling', Name: 'ELN', ByYear: true, ExtraInfo: 'The first 10GB are free', ExtraInfoUrl: '', Url: [ { Name: 'Benchling website', Url: 'https://benchling.com/academic' } ], CatName: 'Options', Cat: { 'Cloud Based 10GB': 0 }, CatUnit: 'CHF' } // Database const MysqlEpfl = { Style: 'CategoryCost', Provider: 'EPFL-VPO-DSI', Name: 'MySql', ByYear: true, Adaptive: false, Url: [ { Name: 'EPFL DSI ', Url: 'https://support.epfl.ch/epfl?id=epfl_service_status&service=eb026fa0db34c700ef64731b8c96198e' } ], CatName: 'Options', Cat: { 'MySQL max 2GB': 0 }, CatUnit: 'CHF' } // Repository const Zenodo = { Style: 'CategoryCost', Provider: 'Zenodo-CERN', Name: 'Zenodo', ByYear: false, Adaptive: false, Url: [ { Name: 'Zenodo Website', Url: 'https://www.zenodo.org/' }, { Name: 'About Zenodo', Url: 'http://about.zenodo.org/' } ], CatName: 'Options', Cat: { 'Max 50GB per Dataset': 0 }, CatUnit: 'CHF' } - // SCITAS will no longer accept new users in 2024 (will close down eventually) const C4science = { Style: 'CategoryCost', Provider: 'EPFL-SCITAS', Name: 'C4Science', Url: [ { Name: 'C4Science Website', Url: 'https://www.c4science.ch/' } ], ByYear: true, Adaptive: false, ExtraInfo: 'C4Science is a code repository and collaboration platform created by SCITAS at EPFL, accessible to all swissuniversities members.', CatName: 'Options', Cat: { 'Free for text file': 0 }, CatUnit: 'CHF' } const GitlabEPFL = { Style: 'CategoryCost', Provider: 'EPFL-VPO-DSI', Name: 'Gitlab EPFL', Url: [ { Name: 'Gitlab EPFL', Url: 'https://gitlab.epfl.ch/' } ], ByYear: true, Adaptive: false, ExtraInfo: 'A Gitlab server is operated by DSI staff at EPFL', CatName: 'Options', Cat: { 'Free for text file': 0 }, CatUnit: 'CHF' } const Github = { Style: 'AmountRatesCost', Provider: 'GitHub', Name: 'GitHub', Url: [ { Name: 'Github Website Pricing', Url: 'https://github.com/pricing' } ], AmountName: 'Number of user', AmountUnit: 'User(s)', Adaptive: true, ByYear: true, AmountMin: [1, 5, 10], AmountMax: [100, 100, 100], AmountStep: [1, 1, 1], AmountFree: [0, 0, 0], AmountFreeCumulative: false, RateName: 'Plan', Rates: { Free: 0, Team: 43, Enterprise: 225 }, RateUnit: 'CHF / Users' } const Bitbucket = { Style: 'AmountRatesCost', Provider: 'Bitbucket', Name: 'BitBucket', Url: [ { Name: 'Bitbucket Website Pricing', Url: 'https://bitbucket.org/product/pricing' } ], AmountName: 'Number of users', AmountUnit: 'User(s)', Adaptive: true, ByYear: true, AmountMin: [1, 5, 5], AmountMax: [5, 100, 100], AmountStep: [1, 1, 1], AmountFree: [0, 0, 0], AmountFreeCumulative: false, RateName: 'Plan', Rates: { 'Free (up to 5 users)': 0, 'Standard for growing teams (min 5 users)': 32, 'Premium for large teams (min 5 users)': 64 }, RateUnit: 'CHF / Users' } const Gitlab = { Style: 'AmountRatesCost', Provider: 'Gitlab', Name: 'Gitlab', Url: [ { Name: 'Gitlab Website Pricing', Url: 'https://about.gitlab.com/pricing/' } ], AmountName: 'Number of users', AmountUnit: 'User(s)', AmountMin: 1, AmountMax: 100, AmountStep: 1, AmountFree: 0, Adaptive: false, AmountFreeCumulative: false, ByYear: true, RateName: 'Plan', Rates: { 'Free (1 user, 5GB)': 0, 'Premium (team, 50 GB)': 310, 'Ultimate (temp, 250 GB)': 1060 }, RateUnit: 'CHF / user' } const Figshare = { Style: 'AmountRatesCost', Provider: 'Figshare', Name: 'Figshare', Url: [ { Name: 'Figshare website', Url: 'https://figshare.com/' }, { Name: 'Figshare Pricing', Url: 'https://www.g2.com/products/figshare/pricing' }, { Name: 'Figshare+ Pricing', Url: 'https://knowledge.figshare.com/plus#pricing' } ], AmountName: 'Volume', AmountUnit: 'GB', Adaptive: true, ByYear: true, AmountMin: [1, 100, 250], AmountMax: [20, 100, 5000], AmountStep: [1, 79, 250], AmountFree: [0, 0, 0], AmountFreeCumulative: false, RateName: 'Plan', Rates: { 'Free (up to 20 GB)': 0, '21-100 GB (flat rate)': 4.0, 'Over 100 GB': 3.1 }, RateUnit: 'CHF / GB' } const Dryad = { Style: 'CategoryAmountRatesCost', Provider: 'Dryad', Name: 'Dyrad', Url: [ { Name: 'Dryad Website Pricing', Url: 'https://datadryad.org/stash/requirements#cost' } ], ByYear: false, ExtraInfo: 'The costs of enabling access to research data under an SNSF grant are eligible. The data archives (data repositories) have to meet the FAIR principles.', ExtraInfoUrl: 'http://www.snf.ch/SiteCollectionDocuments/snsf-general-implementation-regulations-for-the-funding-regulations-e.pdf#page=15', CatName: 'Options', Cat: { 'up to 50GB if DPC covered': 0, 'up to 50GB if no DPC covered': 134 }, CatUnit: 'CHF', AmountName: 'Storage', AmountUnit: 'GB', Adaptive: false, AmountMin: 50, AmountMax: 300, AmountStep: 10, AmountFree: 50, AmountFreeCumulative: false, RateName: 'Storage', Rates: { 'Extra Storage': 4.5 }, RateUnit: 'CHF / GB' } const Acoua = { Style: 'CategoryCost', Provider: 'EPFL', Name: 'ACOUA (Academic Output Archive)', ByYear: false, Adaptive: false, Url: [ { Name: 'About ACOUA', Url: 'https://www.epfl.ch/campus/library/acoua-support/' }, { Name: 'ACOUA (access limited to EPFL network)', Url: 'http://acoua.epfl.ch' } ], ExtraInfo: 'The ACOUA service is exclusively designed for EPFL researchers.', CatName: 'Options', Cat: { 'Costs covered by EPFL-DSI': 0 }, CatUnit: 'CHF' } const EpflS3 = { Style: 'AmountRatesCost', Provider: 'EPFL-DSI', Name: 'EPFL S3', Url: [ { Name: 'Service description', Url: 'https://support.epfl.ch/epfl?id=epfl_kb_article_view&sys_kb_id=e8d10bf997fc7150e55e370f2153af5a' } ], AmountName: 'Volume', AmountUnit: 'TB', AmountMin: 1, AmountMax: 100, AmountStep: 1, AmountFree: 0, Adaptive: false, AmountFreeCumulative: false, ByYear: true, RateName: 'Plan', Rates: { 'Project funded by EPFL budgetary allocation': 29, 'Project funded by H2020 or SNSF': 83.90 }, RateUnit: 'CHF / TB' } // System variable definition // ---------------------------------------------------- // ---------------------------------------------------- const NoneSelected = { Style: 'NoneSelect', Provider: 'None', Name: 'Select a Provider', Url: '', ByYear: false } const UserCostSelect = { Style: 'UserCost', Provider: 'Manual Provider', Name: '', Url: '', ByYear: false } // Categories definition // ---------------------------------------------------- // ---------------------------------------------------- const storage = { Name: 'Active Storage', Icon: 'storage.png', Url: [ { Name: 'EPFL RDM software information', Url: 'https://www.epfl.ch/campus/library/services-researchers/rdm-software/' }, { Name: 'Comparison of file synchronization software', Url: 'https://en.wikipedia.org/wiki/Comparison_of_file_synchronization_software' } ], Data: [NoneSelected, NasEpfl, RcpEpfl, ScitasEpfl, MsPersonalEpfl, MsSharedEpfl, SwitchEpfl, GoogleDriveEdu, DropboxPerso, DropboxTeam, UserCostSelect ] } const ELN = { Name: 'Electronic LabBook', Icon: 'eln.png', Url: [ { Name: 'EPFL RDM software information', Url: 'https://www.epfl.ch/campus/library/services-researchers/rdm-software/' } ], Data: [NoneSelected, SLIMSEpfl, ELNEpfl, Rspace, Benchling, UserCostSelect ] } const Database = { Name: 'Database', Icon: 'database.png', Url: '', Data: [NoneSelected, MysqlEpfl, UserCostSelect ] } const datarepository = { Name: 'Data Repository', Icon: 'drepos.png', Url: [ { Name: 'EPFL RDM software information', Url: 'https://www.epfl.ch/campus/library/services-researchers/rdm-software/' } ], Data: [ NoneSelected, Zenodo, Figshare, Dryad, UserCostSelect ] } const coderepository = { Name: 'Code Repository', Icon: 'crepos.png', Url: '', Data: [ NoneSelected, // C4science, GitlabEPFL, Github, Bitbucket, Gitlab, UserCostSelect ] } const longermarchive = { Name: 'Long-term Archive', Icon: 'archive.png', Url: '', Data: [ NoneSelected, Acoua, EpflS3, UserCostSelect ] } // Combine Categories // ---------------------------------------------------- // ---------------------------------------------------- const MainData = { InstName: 'EPFL Library', InstLogo: 'logo.png', InstLogoWidth: 200, Updated: '2023-12-21', HelpUrl: 'mailto:researchdata@epfl.ch', Currency: 'CHF', OEXRApi: 'cd8d785bdb6646b0a7e4c0eba5a74199', Conv: ['EUR', 'USD', 'GBP'], UseStats: true, StatsEngine: 'matomo', StatsURL: 'costcalc.epfl.ch/matomo/', StatsID: '1', StatsContact: 'mailto:researchdata@epfl.ch', Version: 'v2.2', DefaultDuration: 1, Data: [storage, ELN, Database, datarepository, coderepository, longermarchive] }