diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..591229f8
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+# Editor configuration, see https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+max_line_length = 300
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index 92a30148..00000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "extends": "next/core-web-vitals",
- "rules": {
- "@next/next/no-img-element": "off"
- }
-}
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..dfe07704
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/.gitignore b/.gitignore
index 8f322f0d..943bef2c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,11 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+# See https://help.github.com/ignore-files/ for more about ignoring files.
+
+.sass-cache
+.vscode
# dependencies
/node_modules
-/.pnp
-.pnp.js
+package-lock.json
# testing
/coverage
@@ -11,25 +13,23 @@
# next.js
/.next/
/out/
+tsconfig.tsbuildinfo
# production
/build
+/idea
+/.idea
# misc
.DS_Store
-*.pem
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
-# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
-# local env files
-.env*.local
-
-# vercel
-.vercel
-
-# typescript
-*.tsbuildinfo
-next-env.d.ts
+layout.css
+pages.css
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 9fec8807..00000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,33 +0,0 @@
-# Changelog
-
-## 10.0.0
-
-- Upgrade to Next 13.4.8
-- Migrate to Next App Roter
-- Migrate to PrimeReactContext
-- Update to PrimeReact 9.6.2
-- Update other dependencies
-
-## 9.1.2
-
-- Refactored project files
-
-## 9.1.1
-
-- Fixed hydration warnings
-
-## 9.1.0
-
-- Add typescript support
-
-## 9.0.0
-
-- Upgrade PrimeReact to v9
-- Upgrade to PrimeReact 9.2.2
-- Upgrade to PrimeFlex 3.3.0
-- Upgrade to Next 13.2.3
-- Update other dependencies
-
-## 8.1.0
-
-- Migrate CRA to NextJS
diff --git a/README.md b/README.md
index b2e3085d..b12f3e33 100644
--- a/README.md
+++ b/README.md
@@ -2,23 +2,21 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next
## Getting Started
-Sakai is an application template for Next.js based on the popular Next.js framework with new App Router.
-
First, run the development server:
```bash
npm run dev
# or
yarn dev
-# or
-pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
-## Integration with Existing Next.js Applications
+You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
+
+[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
-Only the folders related to the layout need to be moved into your project. Integration of pages involves moving the files under those folders. Make sure that the using page is defined under the related group layout.
+The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Learn More
@@ -33,4 +31,4 @@ You can check out [the Next.js GitHub repository](https://github.com/vercel/next
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
-Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
\ No newline at end of file
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
diff --git a/app/(full-page)/layout.tsx b/app/(full-page)/layout.tsx
deleted file mode 100644
index 7d5747fe..00000000
--- a/app/(full-page)/layout.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Metadata } from 'next';
-import AppConfig from '../../layout/AppConfig';
-import React from 'react';
-
-interface SimpleLayoutProps {
- children: React.ReactNode;
-}
-
-export const metadata: Metadata = {
- title: 'PrimeReact Sakai',
- description: 'The ultimate collection of design-agnostic, flexible and accessible React UI Components.'
-};
-
-export default function SimpleLayout({ children }: SimpleLayoutProps) {
- return (
-
Next v13, React v18 with PrimeReact v9
- -- Sakai is an application template for React based on the popular{' '} - - NextJS - {' '} - framework with new{' '} - - App Router - - . To get started, clone the{' '} - - repository - {' '} - from GitHub and install the dependencies with npm or yarn. -
-
- {`"npm install" or "yarn"`}
-
-
- - Next step is running the application using the start script and navigate to http://localhost:3000/ to view the application. That is it, you may now start with the development of your application using the Sakai - template. -
- -
- {`"npm run dev" or "yarn dev"`}
-
-
- Dependencies of Sakai are listed below and needs to be defined at package.json.
- -
- {`"primereact": "^9.6.2", //required: PrimeReact components
-"primeicons": "^6.0.1", //required: Icons
-"primeflex": "^3.3.0", //required: Utility CSS classes
-`}
-
-
- Sakai consist of a couple of folders where demos and core layout have been separated.
-- There are two{' '} - - root groups - {' '} - under the app folder; {`(main)`} represents the pages that reside in the main dashboard layout whereas {`(full-page)`}{' '} - groups the pages with full page content such as landing page or a login page. -
-- Root Layout is the main of the application and it is defined at app/layout.tsx file. It contains the style imports and layout context provider. -
-
-
- {`"use client"
-import { LayoutProvider } from "./layout/context/layoutcontext";
-import { PrimeReactProvider } from "primereact/api";
-import "primereact/resources/primereact.css";
-...
-import "../styles/layout/layout.scss";
-import "../styles/demo/Demos.scss";
-
-interface RootLayoutProps {
- children: React.ReactNode;
-}
-
-export default function RootLayout({ children }: RootLayoutProps) {
- return (
-
-
-
-
-
-
- {children}
-
-
-
- );
-}
-
-`}
-
-
- - The pages that are using the layout elements need to be defined under the app/{'(main)'}/ folder. Those pages use the{' '} - app/{'(main)'}/layout.tsx as the root layout. -
-
-
- {`import { Metadata } from 'next';
-import Layout from "../../layout/layout";
-
-interface MainLayoutProps {
- children: React.ReactNode;
-}
-
-export const metadata: Metadata = {
- title: "Sakai by PrimeReact | Free Admin Template for NextJS",
- ...
- };
-
-export default function MainLayout({ children }: MainLayoutProps) {
- return {children} ;
-}
-`}
-
-
- - Only the pages that are using config sidebar wihout layout elements need to be defined under the app/{'(full-page)'}/ folder. Those pages use the{' '} - app/{'(full-page)'}/layout.tsx as the root layout. -
-
-
- {`import { Metadata } from 'next';
-import AppConfig from "../../layout/AppConfig";
-import React from "react";
-
-interface FullPageLayoutProps {
- children: React.ReactNode;
-}
-
-export const metadata: Metadata = {
- title: "Sakai by PrimeReact | Free Admin Template for NextJS",
- ...
- };
-
-export default function FullPageLayout({ children }: FullPageLayoutProps) {
- return (
-
- {children}
-
-
- );
-}
-`}
-
-
- - Initial layout configuration can be defined at the layout/context/layoutcontext.js file, this step is optional and only necessary when customizing the defaults. -
- -
-
- {`"use client";
-import React, { useState } from 'react';
-import Head from 'next/head';
-export const LayoutContext = React.createContext();
-
-export const LayoutProvider = (props) => {
- const [layoutConfig, setLayoutConfig] = useState({
- ripple: false, //toggles ripple on and off
- inputStyle: 'outlined', //default style for input elements
- menuMode: 'static', //layout mode of the menu, valid values are "static" or "overlay"
- colorScheme: 'light', //color scheme of the template, valid values are "light", "dim" and "dark"
- theme: 'lara-light-indigo', //default component theme for PrimeReact
- scale: 14 //size of the body font size to scale the whole application
- });
-}`}
-
-
-
- - Main menu is defined at AppMenu.js file based on{' '} - - MenuModel API - - . -
- -Only the folders related to the layout need to be moved into your project. Integration of pages involves moving the files under those folders. Make sure that the using page is defined under the related group layout.
- -- Sakai theming is based on the PrimeReact theme being used. Default theme is lara-light-indigo. -
- -- In case you'd like to customize the main layout variables, open _variables.scss file under layout folder. Saving the changes will be reflected instantly at your browser. -
- -
-
- {`
-/* General */
-$scale:14px; /* initial font size */
-$borderRadius:12px; /* border radius of layout element e.g. card, sidebar */
-$transitionDuration:.2s; /* transition duration of layout elements e.g. sidebar */
-`}
-
-
- - PrimeReact components internally use{' '} - - PrimeIcons - {' '} - library, the official icons suite from{' '} - - PrimeTek - - . -
-PrimeIcons is available at npm, run the following command to download it to your project.
-
- {`npm install primeicons --save`}
-
- - PrimeIcons use the pi pi-{icon} syntax such as pi pi-check. A standalone icon can be displayed using an element like i or span -
-
-
- {`
-`}
-
-
- Size of the icons can easily be changed using font-size property.
-
-
- {`
-
-`}
-
-
-
-
-
-
- {`
-
-`}
-
-
-
- Special pi-spin class applies continuous rotation to an icon.
-
- {``}
-
-
- - Here is the current list of PrimeIcons, more icons are added periodically. You may also{' '} - - request new icons - {' '} - at the issue tracker. -
-
- {props.code}
-
- )}
+ {blockView === 'CODE' &&
+ {props.children}
+
+ );
+}
diff --git a/demo/service/CountryService.js b/demo/service/CountryService.js
new file mode 100644
index 00000000..b00f8070
--- /dev/null
+++ b/demo/service/CountryService.js
@@ -0,0 +1,13 @@
+import getConfig from 'next/config';
+
+export class CountryService {
+ constructor() {
+ this.contextPath = getConfig().publicRuntimeConfig.contextPath;
+ }
+
+ getCountries() {
+ return fetch(this.contextPath + '/demo/data/countries.json', { headers: { 'Cache-Control': 'no-cache' } })
+ .then((res) => res.json())
+ .then((d) => d.data);
+ }
+}
diff --git a/demo/service/CountryService.tsx b/demo/service/CountryService.tsx
deleted file mode 100644
index e3dbb2f1..00000000
--- a/demo/service/CountryService.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Demo } from '../../types/types';
-
-export const CountryService = {
- getCountries() {
- return fetch('/demo/data/countries.json', { headers: { 'Cache-Control': 'no-cache' } })
- .then((res) => res.json())
- .then((d) => d.data as Demo.Country[]);
- }
-};
diff --git a/demo/service/CustomerService.js b/demo/service/CustomerService.js
new file mode 100644
index 00000000..72ca9c4d
--- /dev/null
+++ b/demo/service/CustomerService.js
@@ -0,0 +1,19 @@
+import getConfig from 'next/config';
+
+export class CustomerService {
+ constructor() {
+ this.contextPath = getConfig().publicRuntimeConfig.contextPath;
+ }
+
+ getCustomersMedium() {
+ return fetch(this.contextPath + '/demo/data/customers-medium.json', { headers: { 'Cache-Control': 'no-cache' } })
+ .then((res) => res.json())
+ .then((d) => d.data);
+ }
+
+ getCustomersLarge() {
+ return fetch(this.contextPath + '/demo/data/customers-large.json', { headers: { 'Cache-Control': 'no-cache' } })
+ .then((res) => res.json())
+ .then((d) => d.data);
+ }
+}
diff --git a/demo/service/CustomerService.tsx b/demo/service/CustomerService.tsx
deleted file mode 100644
index f962123e..00000000
--- a/demo/service/CustomerService.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Demo } from '../../types/types';
-
-export const CustomerService = {
- getCustomersMedium() {
- return fetch('/demo/data/customers-medium.json', { headers: { 'Cache-Control': 'no-cache' } })
- .then((res) => res.json())
- .then((d) => d.data as Demo.Customer[]);
- },
-
- getCustomersLarge() {
- return fetch('/demo/data/customers-large.json', { headers: { 'Cache-Control': 'no-cache' } })
- .then((res) => res.json())
- .then((d) => d.data as Demo.Customer[]);
- }
-};
diff --git a/demo/service/EventService.js b/demo/service/EventService.js
new file mode 100644
index 00000000..52683df0
--- /dev/null
+++ b/demo/service/EventService.js
@@ -0,0 +1,13 @@
+import getConfig from 'next/config';
+
+export class EventService {
+ constructor() {
+ this.contextPath = getConfig().publicRuntimeConfig.contextPath;
+ }
+
+ getEvents() {
+ return fetch('/demo/data/events.json', { headers: { 'Cache-Control': 'no-cache' } })
+ .then((res) => res.json())
+ .then((d) => d.data);
+ }
+}
diff --git a/demo/service/EventService.tsx b/demo/service/EventService.tsx
deleted file mode 100644
index 58e8ebdd..00000000
--- a/demo/service/EventService.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Demo } from '../../types/types';
-
-export const EventService = {
- getEvents() {
- return fetch('/demo/data/events.json', { headers: { 'Cache-Control': 'no-cache' } })
- .then((res) => res.json())
- .then((d) => d.data as Demo.Event);
- }
-};
diff --git a/demo/service/IconService.js b/demo/service/IconService.js
new file mode 100644
index 00000000..d951099b
--- /dev/null
+++ b/demo/service/IconService.js
@@ -0,0 +1,22 @@
+import getConfig from 'next/config';
+
+export class IconService {
+ constructor() {
+ this.icons = [];
+ this.selectedIcon = null;
+ this.contextPath = getConfig().publicRuntimeConfig.contextPath;
+ }
+
+ getIcons() {
+ return fetch(this.contextPath + '/demo/data/icons.json', { headers: { 'Cache-Control': 'no-cache' } })
+ .then((res) => res.json())
+ .then((d) => d.icons);
+ }
+
+ getIcon(id) {
+ if (this.icons) {
+ this.selectedIcon = this.icons.find((x) => x.properties.id === id);
+ return this.selectedIcon;
+ }
+ }
+}
diff --git a/demo/service/IconService.tsx b/demo/service/IconService.tsx
deleted file mode 100644
index 1bb0c856..00000000
--- a/demo/service/IconService.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Demo } from '../../types/types';
-
-let icons: Demo.Icon[] = [];
-let selectedIcon: Demo.Icon | undefined;
-export const IconService = {
- getIcons() {
- return fetch('/demo/data/icons.json', { headers: { 'Cache-Control': 'no-cache' } })
- .then((res) => res.json())
- .then((d) => d.icons as Demo.Icon[]);
- },
-
- getIcon(id: number) {
- if (icons) {
- selectedIcon = icons.find((x: Demo.Icon) => x.properties?.id === id);
- return selectedIcon;
- }
- }
-};
diff --git a/demo/service/NodeService.js b/demo/service/NodeService.js
new file mode 100644
index 00000000..b0f40d02
--- /dev/null
+++ b/demo/service/NodeService.js
@@ -0,0 +1,19 @@
+import getConfig from 'next/config';
+
+export class NodeService {
+ constructor() {
+ this.contextPath = getConfig().publicRuntimeConfig.contextPath;
+ }
+
+ getTreeNodes() {
+ return fetch(this.contextPath + '/demo/data/treenodes.json', { headers: { 'Cache-Control': 'no-cache' } })
+ .then((res) => res.json())
+ .then((d) => d.root);
+ }
+
+ getTreeTableNodes() {
+ return fetch(this.contextPath + '/demo/data/treetablenodes.json', { headers: { 'Cache-Control': 'no-cache' } })
+ .then((res) => res.json())
+ .then((d) => d.root);
+ }
+}
diff --git a/demo/service/NodeService.tsx b/demo/service/NodeService.tsx
deleted file mode 100644
index f30d3209..00000000
--- a/demo/service/NodeService.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { TreeNode } from 'primereact/treenode';
-
-export const NodeService = {
- getFiles() {
- return fetch('/demo/data/files.json', {
- headers: { 'Cache-Control': 'no-cache' }
- })
- .then((res) => res.json())
- .then((d) => d.data as TreeNode[]);
- },
-
- getLazyFiles() {
- return fetch('/demo/data/files-lazy.json', {
- headers: { 'Cache-Control': 'no-cache' }
- })
- .then((res) => res.json())
- .then((d) => d.data as TreeNode[]);
- },
-
- getFilesystem() {
- return fetch('/demo/data/filesystem.json', {
- headers: { 'Cache-Control': 'no-cache' }
- })
- .then((res) => res.json())
- .then((d) => d.data as TreeNode[]);
- },
-
- getLazyFilesystem() {
- return fetch('/demo/data/filesystem-lazy.json', {
- headers: { 'Cache-Control': 'no-cache' }
- })
- .then((res) => res.json())
- .then((d) => d.data as TreeNode[]);
- }
-};
diff --git a/demo/service/PhotoService.js b/demo/service/PhotoService.js
new file mode 100644
index 00000000..a8fb64b8
--- /dev/null
+++ b/demo/service/PhotoService.js
@@ -0,0 +1,13 @@
+import getConfig from 'next/config';
+
+export class PhotoService {
+ constructor() {
+ this.contextPath = getConfig().publicRuntimeConfig.contextPath;
+ }
+
+ getImages() {
+ return fetch(this.contextPath + '/demo/data/photos.json', { headers: { 'Cache-Control': 'no-cache' } })
+ .then((res) => res.json())
+ .then((d) => d.data);
+ }
+}
diff --git a/demo/service/PhotoService.tsx b/demo/service/PhotoService.tsx
deleted file mode 100644
index b4dec9c9..00000000
--- a/demo/service/PhotoService.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Demo } from '../../types/types';
-
-export const PhotoService = {
- getImages() {
- return fetch('/demo/data/photos.json', { headers: { 'Cache-Control': 'no-cache' } })
- .then((res) => res.json())
- .then((d) => d.data as Demo.Photo[]);
- }
-};
diff --git a/demo/service/ProductService.js b/demo/service/ProductService.js
new file mode 100644
index 00000000..36728215
--- /dev/null
+++ b/demo/service/ProductService.js
@@ -0,0 +1,25 @@
+import getConfig from 'next/config';
+
+export class ProductService {
+ constructor() {
+ this.contextPath = getConfig().publicRuntimeConfig.contextPath;
+ }
+
+ getProductsSmall() {
+ return fetch(this.contextPath + '/demo/data/products-small.json', { headers: { 'Cache-Control': 'no-cache' } })
+ .then((res) => res.json())
+ .then((d) => d.data);
+ }
+
+ getProducts() {
+ return fetch(this.contextPath + '/demo/data/products.json', { headers: { 'Cache-Control': 'no-cache' } })
+ .then((res) => res.json())
+ .then((d) => d.data);
+ }
+
+ getProductsWithOrdersSmall() {
+ return fetch(this.contextPath + '/demo/data/products-orders-small.json', { headers: { 'Cache-Control': 'no-cache' } })
+ .then((res) => res.json())
+ .then((d) => d.data);
+ }
+}
diff --git a/demo/service/ProductService.tsx b/demo/service/ProductService.tsx
deleted file mode 100644
index 4d0180c8..00000000
--- a/demo/service/ProductService.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Demo } from '../../types/types';
-
-export const ProductService = {
- getProductsSmall() {
- return fetch('/demo/data/products-small.json', { headers: { 'Cache-Control': 'no-cache' } })
- .then((res) => res.json())
- .then((d) => d.data as Demo.Product[]);
- },
-
- getProducts() {
- return fetch('/demo/data/products.json', { headers: { 'Cache-Control': 'no-cache' } })
- .then((res) => res.json())
- .then((d) => d.data as Demo.Product[]);
- },
-
- getProductsWithOrdersSmall() {
- return fetch('/demo/data/products-orders-small.json', { headers: { 'Cache-Control': 'no-cache' } })
- .then((res) => res.json())
- .then((d) => d.data as Demo.Product[]);
- }
-};
diff --git a/demo/utils/ScrollToTop.js b/demo/utils/ScrollToTop.js
new file mode 100644
index 00000000..50ab92fd
--- /dev/null
+++ b/demo/utils/ScrollToTop.js
@@ -0,0 +1,14 @@
+import { useEffect } from 'react';
+import { useRouter, withRouter } from 'next/router';
+
+function ScrollToTop(props) {
+ const router = useRouter();
+
+ // useEffect(() => {
+ // window.scrollTo(0, 0)
+ // }, [router]);
+
+ return props.children;
+}
+
+export default withRouter(ScrollToTop);
diff --git a/demo/utils/navlink.js b/demo/utils/navlink.js
new file mode 100644
index 00000000..83538afa
--- /dev/null
+++ b/demo/utils/navlink.js
@@ -0,0 +1,33 @@
+import { useRouter } from 'next/router';
+import Link from 'next/link';
+import PropTypes from 'prop-types';
+
+export { NavLink };
+
+NavLink.propTypes = {
+ href: PropTypes.string.isRequired,
+ activeclassname: PropTypes.string,
+ exact: PropTypes.bool,
+ role: PropTypes.string
+};
+
+NavLink.defaultProps = {
+ exact: false
+};
+
+function NavLink({ href, exact, children, role, target, ariaLabel, ...props }) {
+ const { pathname } = useRouter();
+ const isActive = exact ? pathname === href : pathname.startsWith(href);
+
+ if (isActive) {
+ props.className += ' active-route';
+ }
+
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/demo/utils/serviceWorker.js b/demo/utils/serviceWorker.js
new file mode 100644
index 00000000..69abc8b9
--- /dev/null
+++ b/demo/utils/serviceWorker.js
@@ -0,0 +1,122 @@
+// This optional code is used to register a service worker.
+// register() is not called by default.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on subsequent visits to a page, after all the
+// existing tabs open on the page have been closed, since previously cached
+// resources are updated in the background.
+
+// To learn more about the benefits of this model and instructions on how to
+// opt-in, read http://bit.ly/CRA-PWA
+
+const isLocalhost = Boolean(
+ window.location.hostname === 'localhost' ||
+ // [::1] is the IPv6 localhost address.
+ window.location.hostname === '[::1]' ||
+ // 127.0.0.1/8 is considered localhost for IPv4.
+ window.location.hostname?.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
+);
+
+export function register(config) {
+ if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ // The URL constructor is available in all browsers that support SW.
+ const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
+ if (publicUrl.origin !== window.location.origin) {
+ // Our service worker won't work if PUBLIC_URL is on a different origin
+ // from what our page is served on. This might happen if a CDN is used to
+ // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+ return;
+ }
+
+ window.addEventListener('load', () => {
+ const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+ if (isLocalhost) {
+ // This is running on localhost. Let's check if a service worker still exists or not.
+ checkValidServiceWorker(swUrl, config);
+
+ // Add some additional logging to localhost, pointing developers to the
+ // service worker/PWA documentation.
+ navigator.serviceWorker.ready.then(() => {
+ console.log('This web app is being served cache-first by a service ' + 'worker. To learn more, visit http://bit.ly/CRA-PWA');
+ });
+ } else {
+ // Is not localhost. Just register service worker
+ registerValidSW(swUrl, config);
+ }
+ });
+ }
+}
+
+function registerValidSW(swUrl, config) {
+ navigator.serviceWorker
+ .register(swUrl)
+ .then((registration) => {
+ registration.onupdatefound = () => {
+ const installingWorker = registration.installing;
+ if (installingWorker == null) {
+ return;
+ }
+ installingWorker.onstatechange = () => {
+ if (installingWorker.state === 'installed') {
+ if (navigator.serviceWorker.controller) {
+ // At this point, the updated precached content has been fetched,
+ // but the previous service worker will still serve the older
+ // content until all client tabs are closed.
+ console.log('New content is available and will be used when all ' + 'tabs for this page are closed. See http://bit.ly/CRA-PWA.');
+
+ // Execute callback
+ if (config && config.onUpdate) {
+ config.onUpdate(registration);
+ }
+ } else {
+ // At this point, everything has been precached.
+ // It's the perfect time to display a
+ // "Content is cached for offline use." message.
+ console.log('Content is cached for offline use.');
+
+ // Execute callback
+ if (config && config.onSuccess) {
+ config.onSuccess(registration);
+ }
+ }
+ }
+ };
+ };
+ })
+ .catch((error) => {
+ console.error('Error during service worker registration:', error);
+ });
+}
+
+function checkValidServiceWorker(swUrl, config) {
+ // Check if the service worker can be found. If it can't reload the page.
+ fetch(swUrl)
+ .then((response) => {
+ // Ensure service worker exists, and that we really are getting a JS file.
+ const contentType = response.headers.get('content-type');
+ if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) {
+ // No service worker found. Probably a different app. Reload the page.
+ navigator.serviceWorker.ready.then((registration) => {
+ registration.unregister().then(() => {
+ window.location.reload();
+ });
+ });
+ } else {
+ // Service worker found. Proceed as normal.
+ registerValidSW(swUrl, config);
+ }
+ })
+ .catch(() => {
+ console.log('No internet connection found. App is running in offline mode.');
+ });
+}
+
+export function unregister() {
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.ready.then((registration) => {
+ registration.unregister();
+ });
+ }
+}
diff --git a/layout/AppConfig.js b/layout/AppConfig.js
new file mode 100644
index 00000000..1f28261c
--- /dev/null
+++ b/layout/AppConfig.js
@@ -0,0 +1,339 @@
+import getConfig from 'next/config';
+import PrimeReact from 'primereact/api';
+import { Button } from 'primereact/button';
+import { InputSwitch } from 'primereact/inputswitch';
+import { RadioButton } from 'primereact/radiobutton';
+import { Sidebar } from 'primereact/sidebar';
+import { classNames } from 'primereact/utils';
+import React, { useContext, useEffect, useState } from 'react';
+import { LayoutContext } from './context/layoutcontext';
+
+const AppConfig = (props) => {
+ const [scales] = useState([12, 13, 14, 15, 16]);
+ const { layoutConfig, setLayoutConfig, layoutState, setLayoutState } = useContext(LayoutContext);
+ const contextPath = getConfig().publicRuntimeConfig.contextPath;
+
+ const onConfigButtonClick = () => {
+ setLayoutState((prevState) => ({ ...prevState, configSidebarVisible: true }));
+ };
+
+ const onConfigSidebarHide = () => {
+ setLayoutState((prevState) => ({ ...prevState, configSidebarVisible: false }));
+ };
+
+ const changeInputStyle = (e) => {
+ setLayoutConfig((prevState) => ({ ...prevState, inputStyle: e.value }));
+ };
+
+ const changeRipple = (e) => {
+ PrimeReact.ripple = e.value;
+ setLayoutConfig((prevState) => ({ ...prevState, ripple: e.value }));
+ };
+
+ const changeMenuMode = (e) => {
+ setLayoutConfig((prevState) => ({ ...prevState, menuMode: e.value }));
+ };
+
+ const changeTheme = (theme, colorScheme) => {
+ const themeLink = document.getElementById('theme-css');
+ const themeHref = themeLink ? themeLink.getAttribute('href') : null;
+ const newHref = themeHref ? themeHref.replace(layoutConfig.theme, theme) : null;
+
+ replaceLink(themeLink, newHref, () => {
+ setLayoutConfig((prevState) => ({ ...prevState, theme, colorScheme }));
+ });
+ };
+
+ const replaceLink = (linkElement, href, onComplete) => {
+ if (!linkElement || !href) {
+ return;
+ }
+
+ const id = linkElement.getAttribute('id');
+ const cloneLinkElement = linkElement.cloneNode(true);
+
+ cloneLinkElement.setAttribute('href', href);
+ cloneLinkElement.setAttribute('id', id + '-clone');
+
+ linkElement.parentNode.insertBefore(cloneLinkElement, linkElement.nextSibling);
+
+ cloneLinkElement.addEventListener('load', () => {
+ linkElement.remove();
+
+ const element = document.getElementById(id); // re-check
+ element && element.remove();
+
+ cloneLinkElement.setAttribute('id', id);
+ onComplete && onComplete();
+ });
+ };
+
+ const decrementScale = () => {
+ setLayoutConfig((prevState) => ({ ...prevState, scale: prevState.scale - 1 }));
+ };
+
+ const incrementScale = () => {
+ setLayoutConfig((prevState) => ({ ...prevState, scale: prevState.scale + 1 }));
+ };
+
+ const applyScale = () => {
+ document.documentElement.style.fontSize = layoutConfig.scale + 'px';
+ };
+
+
+ useEffect(() => {
+ applyScale();
+ }, [layoutConfig.scale]);
+
+ return (
+ <>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-React 17.x and PrimeReact 7.x
+ ++ Sakai is an application template for React based on the popular NextJS framework. To get started, clone the repository from + GitHub and install the dependencies with npm or yarn. +
++ Next step is running the application using the start script and navigate to http://localhost:3000/ to view the application. That is it, you may now start with the development of your application using the Sakai + template. +
+ +Dependencies of Sakai are listed below and needs to be defined at package.json.
+ +Sakai consists of a couple folders, demos and core has been separated so that you can easily remove what is not necessary for your application.
+Main menu is defined at AppMenu.js file based on MenuModel API.
+ +Only the folders that are related to the layout needs to move in to your project. We've created a short tutorial with details.
+ ++ Sakai theming is based on the PrimeReact theme being used. Default theme is lara-light-indigo. +
+ ++ In case you'd like to customize the main layout variables, open _variables.scss file under src/layout folder. Saving the changes will be reflected instantly at your browser. +
+ +