diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..14d9aa2 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/env", "@babel/preset-react"] +} \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 0000000..1496cc2 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +# Defines the Output Bundle Path for the "npm run dev" target. +# It is recommended to point it directly to the SPM jscript folder. +# For example: %SPM_ROOT_PATH%/webclient/WebContent/CDEJ/jscript/SPMUIComponents diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f23a460 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Dependency directories +node_modules/ +dist/ + +# storybook +storybook-static/ \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..8c0cfc5 --- /dev/null +++ b/.npmignore @@ -0,0 +1,15 @@ +node_modules + +.publish + +.editorconfig +.eslintignore +.eslintrc +.travis.yml + +gulpfile.js +index.js +webpack.config.js + +CONTRIBUTING.md +README.md diff --git a/.storybook/main.js b/.storybook/main.js new file mode 100644 index 0000000..26dfeaf --- /dev/null +++ b/.storybook/main.js @@ -0,0 +1,10 @@ +module.exports = { + "stories": [ + "../src/**/*.stories.mdx", + "../src/**/*.stories.@(js|jsx|ts|tsx)" + ], + "addons": [ + "@storybook/addon-links", + "@storybook/addon-essentials" + ] +} \ No newline at end of file diff --git a/.storybook/preview.js b/.storybook/preview.js new file mode 100644 index 0000000..1a611ec --- /dev/null +++ b/.storybook/preview.js @@ -0,0 +1,11 @@ +import '../src/scss/index.scss'; + +export const parameters = { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, +} \ No newline at end of file diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js new file mode 100644 index 0000000..d2f0454 --- /dev/null +++ b/.storybook/webpack.config.js @@ -0,0 +1,17 @@ +const path = require('path'); +const glob = require('glob'); +const custom = require('../webpack.config.js'); + +module.exports = async ({ config, mode }) => { + config.module.rules.push({ + test: /\.scss$/, + loaders: ['style-loader', 'css-loader', 'sass-loader'], + include: path.resolve(__dirname, '../'), + }); + + return { + ...config, + module: { ...config.module }, + devServer: custom.devServer, + }; +}; diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..64710f8 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,69 @@ +/* + * Licensed Materials - Property of IBM + * + * PID 5725-H26 + * + * Copyright IBM Corporation 2020. All Rights Reserved. + * + * US Government Users Restricted Rights - Use, duplication or disclosure + * restricted by GSA ADP Schedule Contract with IBM Corp. + */ + +/* eslint-disable no-console */ +const path = require('path'); +const gulp = require('gulp'); +const shell = require('shelljs'); + +// Load env vars from the .env file. +require('dotenv').config(); + +/* + * This task generates a development bundle to the specified output folder. + * Also the bundle is generated, it watches the projects folder for changes. + * Any changes in the project kicks off a Delta bundle generator only for + * the file that was changed. + * + * - The output folder is defined in the .env file + * through the variable: DEV_BUNDLE_OUTPUT + * If not variable is defined, it defaults to /dist + * + * - The development bundle uses the source maps strategy "eval-source-map" + * It is the slowest build option but it enables the developer to debug + * on the browser exactly the same code he sees in the code editor + * before it is transpiled by Babel. + */ + +gulp.task('dev:spm', () => { + const output = + process.env.DEV_BUNDLE_OUTPUT || + path.resolve(__dirname, '/dist'); + if (!process.env.DEV_BUNDLE_OUTPUT) { + shell.echo( + `\n[WARNING] Env var DEV_BUNDLE_OUTPUT not defined in the .env file. +Using Default Output: ${output}` + ); + } + + shell.echo(`\n[INFO] Generating the dev bundle to path: ${output} +[INFO] Any changes to the files will automatically trigger a new bundle generation.`); + + shell.exec( + `webpack --mode=development --devtool=eval-source-map\ + --output-path=${output} --watch=true --hide-modules=true\ + --build-delimiter="\n\n[INFO] Bundle Generated into ${output} \n[INFO] Watching for file changes."`, + { fatal: true } + ); +}); + +gulp.task('prod:spm', (done) => { + const output = path.resolve(__dirname, '/dist'); + + shell.echo(`\n[INFO] Generating the dev bundle to path: ${output}.`); + + shell.exec( + `webpack --mode production`, + { fatal: true, silent: true } + ); + done(); +}); + diff --git a/package.json b/package.json new file mode 100644 index 0000000..f3d1830 --- /dev/null +++ b/package.json @@ -0,0 +1,54 @@ +{ + "name": "custom-carbon-addons", + "version": "0.0.1", + "description": "Enable customers to develop the content within the package, Carbon Components, and GraphQl using the apollo client. It also enables customer to build and deploy this new package.", + "main": "index.js", + "scripts": { + "build": "gulp prod:spm", + "dev": "gulp dev:spm", + "test": "echo \"Error: no test specified\" && exit 1", + "start": "webpack-dev-server --mode development", + "storybook": "start-storybook -p 6006", + "build-storybook": "build-storybook" + }, + "author": "Tom Delahunty", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.0", + "@babel/core": "^7.1.0", + "@babel/preset-env": "^7.1.0", + "@babel/preset-react": "^7.0.0", + "@storybook/addon-actions": "^6.3.8", + "@storybook/addon-essentials": "^6.3.8", + "@storybook/addon-links": "^6.3.8", + "@storybook/react": "^6.3.8", + "babel-loader": "^8.0.2", + "css-loader": "^1.0.0", + "gulp": "^4.0.2", + "gulp-cli": "^2.3.0", + "node-sass": "^4.14.1", + "sass-loader": "^10.0.3", + "shelljs": "^0.8.4", + "style-loader": "^0.23.0", + "webpack": "4.42.1", + "webpack-cli": "3.3.11", + "webpack-dev-server": "^3.11.0" + }, + "dependencies": { + "@carbon/grid": "^10.20.0", + "@carbon/icon-helpers": "^10.13.0", + "@carbon/icons": "^10.25.0", + "@carbon/icons-react": "^10.25.0", + "@carbon/layout": "^10.17.0", + "@carbon/pictograms-react": "^11.2.0", + "@carbon/type": "^10.20.0", + "carbon-components": "10.44.0", + "carbon-components-react": "7.41.0", + "carbon-icons": "^7.0.7", + "classnames": "^2.2.6", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-app-polyfill": "^1.0.6", + "regenerator-runtime": "^0.13.7" + } +} diff --git a/public-path.js b/public-path.js new file mode 100644 index 0000000..7c0ca7a --- /dev/null +++ b/public-path.js @@ -0,0 +1,22 @@ +// Path to SPMUIComponents directory (relative to WebContent folder). +const spmUIComponentsBaseURL = 'CDEJ/jscript/SPMUIComponents/'; + +// Retrieves Static Content Server from SPM. +// +// The serverRootURL is set on the root document window so if a js bundle +// is requested by a UIM iframe the parent window is checked instead. +const serverRootURL = + window.curam || window.parent.curam + ? window.curam.serverRootURL || window.parent.curam.serverRootURL || '' + : ''; + +// If a Static Content Server URL is not set, '../' must be prepended to +// the URL to get the correct relative path. +const spmUIComponentsRootURL = serverRootURL + ? spmUIComponentsBaseURL + : `../${spmUIComponentsBaseURL}`; + +// Concatenate the correct public path for use in webpack bundles. +const publicPath = `${serverRootURL}${spmUIComponentsRootURL}`; + +export default __webpack_public_path__ = publicPath; diff --git a/src/dynamicExports.js b/src/dynamicExports.js new file mode 100644 index 0000000..299cc99 --- /dev/null +++ b/src/dynamicExports.js @@ -0,0 +1,19 @@ + +/* + Use when the main bundle is not on the same level as the script calling it. + Webpack will reference this path when emitting the chunks +*/ +export const usePublicPath = async (path) => { + if (path) { + __webpack_public_path__ = path; // eslint-disable-line + } + }; + + +/* Imports Carbon Addons dynamically */ +export const requireCustomCarbonAddons = async () => { + const customCarbonAddons = await import( + /* webpackChunkName: "simple" */ './react' + ); + return customCarbonAddons; + }; \ No newline at end of file diff --git a/src/examples/images/sample-logo.jpg b/src/examples/images/sample-logo.jpg new file mode 100644 index 0000000..77b8adb Binary files /dev/null and b/src/examples/images/sample-logo.jpg differ diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..f389ce3 --- /dev/null +++ b/src/index.js @@ -0,0 +1,21 @@ +import "./scss/index.scss"; + + + + +import 'regenerator-runtime/runtime'; + + import { + requireCustomCarbonAddons, + } from './dynamicExports'; + + + export default { + + requireCustomCarbonAddons, + + }; + + // export { default as requireCustomCarbonAddons } from requireCustomCarbonAddons; + + \ No newline at end of file diff --git a/src/react/Logo/Logo.js b/src/react/Logo/Logo.js new file mode 100644 index 0000000..956e41b --- /dev/null +++ b/src/react/Logo/Logo.js @@ -0,0 +1,43 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import settings from '../settings'; + +const Logo = ({ children, className, size, ...other }) => { + const styleClass = cx( + `${settings.prefix}--logo`, + { + [`${settings.prefix}--logo--large`]: size === 'large', + [`${settings.prefix}--logo--medium`]: size === 'medium', + [`${settings.prefix}--logo--small`]: size === 'small', + }, + className + ); + return ( +
+ {children} +
+ ); +}; + +Logo.propTypes = { + /** + * Pass in the image that will be rendered within the Logo + */ + children: PropTypes.node.isRequired, + /** + * Additional styling + */ + className: PropTypes.string, + /** + * Specify an optional size for the Logo. Defaults to 'medium' + */ + size: PropTypes.oneOf(['small', 'medium', 'large']), +}; + +Logo.defaultProps = { + size: 'medium', + className: undefined, +}; + +export default Logo; diff --git a/src/react/Logo/Logo.stories.js b/src/react/Logo/Logo.stories.js new file mode 100644 index 0000000..0f856f7 --- /dev/null +++ b/src/react/Logo/Logo.stories.js @@ -0,0 +1,70 @@ +import React from 'react'; +import Example from '../../examples/images/sample-logo.jpg'; +import Logo from './Logo'; + + +export default { + title: 'Logo', + component: Logo, +} + +//πŸ‘‡ We create a β€œtemplate” of how args map to rendering +const Template = (args) =>
logo
; + +//πŸ‘‡ Each story then reuses that template + +export const Small = Template.bind({}); + +Small.args = { +size: "small", +}; + +export const Medium = Template.bind({}); + +Medium.args = { +size: "medium", +}; + +export const Large = Template.bind({}); + +Large.args = { +size: "large", +}; + + + + +export const All = () => { + return ( +
+
+
+
+

small

+ + small logo + +
+
+
+
+

medium

+ + medium logo + +
+
+
+
+

large

+ + large logo + +
+
+
+
+ ); +}; + + diff --git a/src/react/Logo/index.js b/src/react/Logo/index.js new file mode 100644 index 0000000..550b5be --- /dev/null +++ b/src/react/Logo/index.js @@ -0,0 +1,26 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Logo from './Logo'; + +const getWrapper = (iframeWindow, containerID) => { + console.log(`${containerID}`); + return iframeWindow.document.getElementById(`${containerID}`); +} + +const render = ({ + inputId, + iframeWindow = window, + logo, + size, +}) => { + const container = getWrapper(iframeWindow, inputId); + + ReactDOM.render( + + small logo + , + container + ); +}; + +export { render }; diff --git a/src/react/PersonFolio/PersonFolio.js b/src/react/PersonFolio/PersonFolio.js new file mode 100644 index 0000000..0315e69 --- /dev/null +++ b/src/react/PersonFolio/PersonFolio.js @@ -0,0 +1,46 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import settings from '../settings'; + +const PersonFolio = ({ children, className, age, ...other }) => { + const styleClass = cx( + `${settings.prefix}--personfolio`, + { + [`${settings.prefix}--personfolio--child`]: age < 18, + [`${settings.prefix}--personfolio--youngadult`]: age >= 18 && age < 25, + [`${settings.prefix}--personfolio--adult`]: age >= 25 && age < 65, + [`${settings.prefix}--personfolio--senior`]: age >= 65, + }, + className + ); + + const {firstname, surname} = other; + return ( +
+

First name: {firstname}

+

Surname: {surname}

+

Age: {age}

+ {children} +
+ ); + }; + + + PersonFolio.propTypes = { + /** + * Pass in the image that will be rendered within the Avatar + */ + children: PropTypes.node, + /** + * Additional styling + */ + className: PropTypes.string, + + }; + + PersonFolio.defaultProps = { + className: undefined, + }; + +export default PersonFolio; \ No newline at end of file diff --git a/src/react/PersonFolio/PersonFolio.stories.js b/src/react/PersonFolio/PersonFolio.stories.js new file mode 100644 index 0000000..5713c03 --- /dev/null +++ b/src/react/PersonFolio/PersonFolio.stories.js @@ -0,0 +1,74 @@ +import React from 'react'; +import PersonFolio from './PersonFolio'; + + +//πŸ‘‡ We create a β€œtemplate” of how args map to rendering +const Template = (args) =>
; + +//πŸ‘‡ Each story then reuses that template + +export const Child = Template.bind({}); + +Child.args = { +age: "12", +}; + +export const YoungAdult = Template.bind({}); + +YoungAdult.args = { +age: "20", +}; + +export const Adult = Template.bind({}); + +Adult.args = { +age: "32", +}; + +export const Senior = Template.bind({}); + +Senior.args = { +age: "71", +}; + +export const PersonFolioStory = () => { + return ( +
+
+
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+
+
+ ); +}; + + +export default { + title: 'Person Folio Story', + component: PersonFolio, +} \ No newline at end of file diff --git a/src/react/PersonFolio/index.js b/src/react/PersonFolio/index.js new file mode 100644 index 0000000..028f5e8 --- /dev/null +++ b/src/react/PersonFolio/index.js @@ -0,0 +1,25 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import PersonFolio from './PersonFolio'; + +const getWrapper = (iframeWindow, containerID) => { + return iframeWindow.document.getElementById(`${containerID}`); +} + +const render = ({ + inputId, + iframeWindow = window, + firstname, + surname, + age, +}) => { + const container = getWrapper(iframeWindow, inputId); + + ReactDOM.render( + , + container + ); +}; + +export { render }; + diff --git a/src/react/index.js b/src/react/index.js new file mode 100644 index 0000000..ce3b04c --- /dev/null +++ b/src/react/index.js @@ -0,0 +1,7 @@ +import 'regenerator-runtime/runtime'; +import * as Logo from './Logo'; +export { Logo }; +import * as PersonFolio from './PersonFolio'; +export { PersonFolio }; + + diff --git a/src/react/settings.js b/src/react/settings.js new file mode 100644 index 0000000..a3023e9 --- /dev/null +++ b/src/react/settings.js @@ -0,0 +1,16 @@ +/* + * Licensed Materials - Property of IBM + * + * PID 5725-H26 + * + * Copyright IBM Corporation 2020. All Rights Reserved. + * + * US Government Users Restricted Rights - Use, duplication or disclosure + * restricted by GSA ADP Schedule Contract with IBM Corp. + */ + +const settings = { + prefix: 'bx', +}; + +module.exports = settings; diff --git a/src/scss/index.scss b/src/scss/index.scss new file mode 100644 index 0000000..a0a2520 --- /dev/null +++ b/src/scss/index.scss @@ -0,0 +1,127 @@ + +//------------------------- +// πŸš€ Variables +//------------------------- + +$feature-flags: ( + grid-columns-16: true, +); + +$css--font-face: true; +$css--helpers: true; +$css--body: true; +$css--reset: true; +$css--default-type: true; +$css--plex: true; +$css--use-experimental-grid: false; +$css--use-experimental-grid-fallback: false; + +//------------------------- +// πŸ”„ Resets +//------------------------- + +// rtl directives must be imported when wrapping an imported stylesheet so +// that they are positioned correctly in the compiled css +// https://github.com/MohammadYounes/rtlcss/issues/113#issuecomment-379907981 + +@import 'carbon-components/scss/globals/scss/css--reset'; + + +//------------------------- +// 🎨 Themes +//------------------------- + +@import 'carbon-components/scss/globals/scss/theme'; + +$carbon--theme: $carbon--theme--g10; +@include carbon--theme(); + +// overwrite box-sizing from css--reset to original Curam styling +* { + box-sizing: content-box; +} + +// Temporary until Carbon fix is applied for date picker opening event. +html.spm-modal--html { + overflow: visible; +} + +.spm-custom-component { + + // re-apply Carbon box-boxing after overwrite above + * { + box-sizing: border-box; + } + + //------------------------- + // 🌍 Globals + //------------------------- + @import 'carbon-components/scss/globals/scss/theme-tokens'; + @import 'carbon-components/scss/globals/scss/layout'; + @import 'carbon-components/scss/globals/scss/layer'; + @import 'carbon-components/scss/globals/scss/typography'; + @import 'carbon-components/scss/globals/scss/css--font-face'; + @import 'carbon-components/scss/globals/scss/css--helpers'; + @import 'carbon-components/scss/globals/scss/css--body'; + @import 'carbon-components/scss/globals/grid/grid'; + + //------------------------- + // πŸ• Components + //------------------------- + + @import 'carbon-components/scss/components/button/button'; + @import 'carbon-components/scss/components/copy-button/copy-button'; + @import 'carbon-components/scss/components/file-uploader/file-uploader'; + @import 'carbon-components/scss/components/checkbox/checkbox'; + @import 'carbon-components/scss/components/combo-box/combo-box'; + @import 'carbon-components/scss/components/radio-button/radio-button'; + @import 'carbon-components/scss/components/toggle/toggle'; + @import 'carbon-components/scss/components/search/search'; + @import 'carbon-components/scss/components/select/select'; + @import 'carbon-components/scss/components/text-input/text-input'; + @import 'carbon-components/scss/components/text-area/text-area'; + @import 'carbon-components/scss/components/number-input/number-input'; + @import 'carbon-components/scss/components/form/form'; + @import 'carbon-components/scss/components/link/link'; + @import 'carbon-components/scss/components/list-box/list-box'; + @import 'carbon-components/scss/components/list/list'; + @import 'carbon-components/scss/components/data-table/data-table'; + @import 'carbon-components/scss/components/structured-list/structured-list'; + @import 'carbon-components/scss/components/code-snippet/code-snippet'; + @import 'carbon-components/scss/components/overflow-menu/overflow-menu'; + @import 'carbon-components/scss/components/content-switcher/content-switcher'; + @import 'carbon-components/scss/components/date-picker/date-picker'; + @import 'carbon-components/scss/components/dropdown/dropdown'; + @import 'carbon-components/scss/components/loading/loading'; + @import 'carbon-components/scss/components/modal/modal'; + @import 'carbon-components/scss/components/multi-select/multi-select'; + @import 'carbon-components/scss/components/notification/inline-notification'; + @import 'carbon-components/scss/components/notification/toast-notification'; + @import 'carbon-components/scss/components/tooltip/tooltip'; + @import 'carbon-components/scss/components/tabs/tabs'; + @import 'carbon-components/scss/components/tag/tag'; + @import 'carbon-components/scss/components/pagination/pagination'; + @import 'carbon-components/scss/components/accordion/accordion'; + @import 'carbon-components/scss/components/progress-indicator/progress-indicator'; + @import 'carbon-components/scss/components/breadcrumb/breadcrumb'; + @import 'carbon-components/scss/components/toolbar/toolbar'; + @import 'carbon-components/scss/components/time-picker/time-picker'; + @import 'carbon-components/scss/components/slider/slider'; + @import 'carbon-components/scss/components/tile/tile'; + @import 'carbon-components/scss/components/skeleton/skeleton'; + @import 'carbon-components/scss/components/inline-loading/inline-loading'; + @import 'carbon-components/scss/components/pagination-nav/pagination-nav'; + + // type utilities + @import '@carbon/type/scss/classes'; + @include carbon--type-classes(); + + //------------------------- + // ✨ Addons + //------------------------- + + + @import './variables.scss'; + @import './logo.scss'; + @import './personfolio.scss'; +} diff --git a/src/scss/logo.scss b/src/scss/logo.scss new file mode 100644 index 0000000..c086664 --- /dev/null +++ b/src/scss/logo.scss @@ -0,0 +1,29 @@ +.#{$prefix}--logo { + box-shadow: inset 0 0 0 2px $carbon--white-0; + border-radius: $border-radius-round; + font-weight: 500; + overflow: hidden; + + &--small { + height: $width-logo-small; + width: $width-logo-small; + min-width: $width-logo-small; + } + + &--medium { + height: $width-logo-medium; + width: $width-logo-medium; + min-width: $width-logo-medium; + } + + &--large { + height: $width-logo-large; + width: $width-logo-large; + min-width: $width-logo-large; + } + + img { + width: 100%; + height: 100%; + } +} diff --git a/src/scss/personfolio.scss b/src/scss/personfolio.scss new file mode 100644 index 0000000..4d6eda3 --- /dev/null +++ b/src/scss/personfolio.scss @@ -0,0 +1,33 @@ +.#{$prefix}--personfolio { + + background-color: $ui-01; + color: $text-01; + border-radius: 6px; + border-width: 1px 1px 1px 1px; + border-style: solid !important; + padding: $spacing-05; + margin-top: $spacing-05; + + &--child { + border-color: $support-01; + + } + + &--youngadult { + + border-color: $support-02; + + } + + &--adult { + + border-color: $support-03; + } + + &--senior { + + border-color: $support-04; + } + + } + \ No newline at end of file diff --git a/src/scss/variables.scss b/src/scss/variables.scss new file mode 100644 index 0000000..37ef7f0 --- /dev/null +++ b/src/scss/variables.scss @@ -0,0 +1,5 @@ +$border-radius-round: 100vw !default; +$width-logo-large: 120px; // do we need small and medium too +$width-logo-medium: 80px; +$width-logo-small: 36px; +$border-base: 1px solid #f3f3f3; // this should be a variable \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..cf1237d --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,45 @@ +const path = require("path"); +const webpack = require("webpack"); + +module.exports = { + entry: ['./public-path.js', "./src/index.js"], + mode: "development", + module: { + rules: [ + { + test: /\.scss$/, + loaders: ['style-loader', 'css-loader', 'sass-loader'], + include: path.resolve(__dirname, './src/scss'), + }, + { + test: /\.(js|jsx)$/, + exclude: /(node_modules|bower_components)/, + loader: "babel-loader", + options: { presets: ["@babel/env"] } + }, + { + test: /\.css$/, + use: ["style-loader", "css-loader"] + }, + { + test: /\.(gif|png|jpe?g|svg)$/i, + loader: 'file-loader', + }, + ] + }, + resolve: { extensions: ["*", ".js", ".jsx"] }, + output: { + path: path.resolve(__dirname, "dist/"), + library: 'spmcustom', + libraryExport: 'default', + publicPath: "/dist/", + filename: "custom-carbon-addons-[name].bundle.js", + chunkFilename: 'custom-carbon-addons-[name].chunk.js', + }, + devServer: { + contentBase: path.join(__dirname, "public/"), + port: 3000, + publicPath: "http://localhost:3000/dist/", + hotOnly: false + } +}; \ No newline at end of file