Skip to content

Commit bcf3783

Browse files
authored
Merge pull request #49 from pnp/quicklinks
Quicklinks
2 parents 8e2b147 + e5aef28 commit bcf3783

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1578
-680
lines changed

iframe-sandbox-app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"private": true,
55
"homepage": ".",
66
"dependencies": {
7+
"@fluentui/react": "^8.104.1",
78
"@microsoft/mgt": "^2.9.0",
89
"@microsoft/mgt-element": "^2.9.0",
910
"@microsoft/mgt-react": "^2.9.0",
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<link rel="icon" href="/favicon.ico" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
<meta name="theme-color" content="#000000" />
8+
<meta
9+
name="description"
10+
content="Web site created using create-react-app"
11+
/>
12+
<link rel="apple-touch-icon" href="/logo192.png" />
13+
<!--
14+
manifest.json provides metadata used when your web app is installed on a
15+
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
16+
17+
<link rel="manifest" href="/manifest.json" />
18+
-->
19+
<!--
20+
Notice the use of in the tags above.
21+
It will be replaced with the URL of the `public` folder during the build.
22+
Only files inside the `public` folder can be referenced from the HTML.
23+
24+
Unlike "/favicon.ico" or "favicon.ico", "/favicon.ico" will
25+
work correctly both with client-side routing and a non-root public URL.
26+
Learn how to configure a non-root public URL by running `npm run build`.
27+
-->
28+
<title>React App</title>
29+
</head>
30+
<body>
31+
<noscript>You need to enable JavaScript to run this app.</noscript>
32+
<div id="root"></div>
33+
<!--
34+
This HTML file is a template.
35+
If you open it directly in the browser, you will see an empty page.
36+
37+
You can add webfonts, meta tags, or analytics to this file.
38+
The build step will place the bundled scripts into the <body> tag.
39+
40+
To begin the development, run `npm start` or `yarn start`.
41+
To create a production bundle, use `npm run build` or `yarn build`.
42+
-->
43+
<script src="js/bundle.js"></script><script src="js/vendors~main.chunk.js"></script><script src="js/main.chunk.js"></script></body>
44+
</html>

iframe-sandbox-app/src/App.tsx

Lines changed: 4 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,12 @@
1-
import React, { useEffect, useState } from 'react';
2-
import * as PREVIEW_REACT from 'react'
3-
import * as PREVIEW_MGT from '@microsoft/mgt'
4-
import * as PREVIEW_MGT_ELEMENT from '@microsoft/mgt-element'
5-
import * as PREVIEW_MGT_REACT from '@microsoft/mgt-react'
6-
import { CacheService, Providers, ProviderState, SimpleProvider } from '@microsoft/mgt-react'
7-
81
import './App.css';
9-
import {
10-
LiveProvider,
11-
LiveError,
12-
LivePreview,
13-
} from 'react-live'
142
import { HashRouter, Route } from 'react-router-dom';
15-
16-
const scope = {
17-
PREVIEW_REACT,
18-
PREVIEW_MGT_REACT,
19-
PREVIEW_MGT,
20-
PREVIEW_MGT_ELEMENT
21-
}
22-
23-
function MGTIframe() {
24-
25-
const [code, setCode] = useState('')
26-
27-
const onMessageReceivedFromIframe = React.useCallback(
28-
(event: any) => {
29-
let data = JSON.parse(event.data);
30-
if (data.code) {
31-
setCode(data.code)
32-
}
33-
console.log("onMessageReceivedFromIframe", event.data);
34-
},
35-
[]
36-
);
37-
38-
useEffect(() => {
39-
window.addEventListener("message", onMessageReceivedFromIframe);
40-
console.log('registered listener')
41-
return () =>
42-
console.log('removing listener, but not')
43-
// window.removeEventListener("message", onMessageReceivedFromIframe);
44-
}, [onMessageReceivedFromIframe]);
45-
46-
CacheService.config.isEnabled = false
47-
48-
var getAccessToken = async (scopes: any[]): Promise<string> => {
49-
return new Promise(function (resolve) {
50-
window.parent.postMessage(JSON.stringify({
51-
scopes: scopes,
52-
}), '*')
53-
window.addEventListener("message", function dataReady(event) {
54-
let data = JSON.parse(event.data);
55-
if (data.token) {
56-
resolve(data.token);
57-
}
58-
});
59-
})
60-
}
61-
62-
var login = async () => {
63-
Providers.globalProvider.setState(ProviderState.SignedIn)
64-
}
65-
66-
var logout = async () => {
67-
window.parent.postMessage(JSON.stringify({
68-
logout: true,
69-
}), '*')
70-
Providers.globalProvider.setState(ProviderState.SignedOut)
71-
}
72-
73-
Providers.globalProvider = new SimpleProvider(getAccessToken, login, logout);
74-
75-
return (
76-
<LiveProvider code={code} scope={scope}>
77-
<LivePreview className='viewer' />
78-
<LiveError
79-
className='error'
80-
/>
81-
</LiveProvider>
82-
);
83-
}
3+
import PopUp from './popup/PopUp';
4+
import MGTIframe from './mgtiframe/MGTIframe';
845

856
export default function App() {
867
return (
878
<HashRouter>
889
<Route path='/mgtiframe'><MGTIframe /></Route>
89-
</HashRouter>);
10+
<Route path='/popup'><PopUp /></Route>
11+
</HashRouter>)
9012
}

iframe-sandbox-app/src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ ReactDOM.render(
1010
<React.StrictMode>
1111
<BrowserRouter>
1212
<App />
13-
</BrowserRouter>,
13+
</BrowserRouter>
1414
</React.StrictMode>,
1515
document.getElementById('root'),
1616
)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { CacheService, Providers, ProviderState, SimpleProvider } from '@microsoft/mgt-react';
3+
import {
4+
LiveProvider,
5+
LiveError,
6+
LivePreview
7+
} from 'react-live';
8+
import * as PREVIEW_REACT from 'react'
9+
import * as PREVIEW_MGT from '@microsoft/mgt'
10+
import * as PREVIEW_MGT_ELEMENT from '@microsoft/mgt-element'
11+
import * as PREVIEW_MGT_REACT from '@microsoft/mgt-react'
12+
13+
export const scope = {
14+
PREVIEW_REACT,
15+
PREVIEW_MGT_REACT,
16+
PREVIEW_MGT,
17+
PREVIEW_MGT_ELEMENT
18+
}
19+
20+
const MGTIframe = () => {
21+
22+
const [code, setCode] = useState('');
23+
24+
const onMessageReceivedFromIframe = React.useCallback(
25+
(event: any) => {
26+
let data = JSON.parse(event.data);
27+
if (data.code) {
28+
setCode(data.code);
29+
}
30+
console.log("onMessageReceivedFromIframe", event.data);
31+
},
32+
[]
33+
);
34+
35+
useEffect(() => {
36+
window.addEventListener("message", onMessageReceivedFromIframe);
37+
console.log('registered listener');
38+
return () => console.log('removing listener, but not');
39+
// window.removeEventListener("message", onMessageReceivedFromIframe);
40+
}, [onMessageReceivedFromIframe]);
41+
42+
CacheService.config.isEnabled = false;
43+
44+
var getAccessToken = async (scopes: any[]): Promise<string> => {
45+
return new Promise(function (resolve) {
46+
window.parent.postMessage(JSON.stringify({
47+
scopes: scopes,
48+
}), '*');
49+
window.addEventListener("message", function dataReady(event) {
50+
let data = JSON.parse(event.data);
51+
if (data.token) {
52+
resolve(data.token);
53+
}
54+
});
55+
});
56+
};
57+
58+
var login = async () => {
59+
Providers.globalProvider.setState(ProviderState.SignedIn);
60+
};
61+
62+
var logout = async () => {
63+
window.parent.postMessage(JSON.stringify({
64+
logout: true,
65+
}), '*');
66+
Providers.globalProvider.setState(ProviderState.SignedOut);
67+
};
68+
69+
Providers.globalProvider = new SimpleProvider(getAccessToken, login, logout);
70+
71+
return (
72+
<LiveProvider code={code} scope={scope}>
73+
<LivePreview className='viewer' />
74+
<LiveError
75+
className='error' />
76+
</LiveProvider>
77+
);
78+
}
79+
80+
export default MGTIframe
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { IScrollablePaneStyles, ISeparatorStyles, ScrollablePane, ScrollbarVisibility, Separator } from '@fluentui/react';
2+
import ChangePageLayout from './ChangePageLayout';
3+
4+
export interface IQuickLinkListProps {
5+
ctx: any,
6+
plo: any,
7+
tabId: number,
8+
}
9+
10+
const separatorStyles: ISeparatorStyles = {
11+
root: {
12+
fontWeight: 'bold'
13+
},
14+
content: undefined
15+
}
16+
17+
const scrollablePaneStyles: IScrollablePaneStyles = {
18+
root: {
19+
marginTop: 50
20+
},
21+
stickyAbove: undefined,
22+
stickyBelow: undefined,
23+
stickyBelowItems: undefined,
24+
contentContainer: undefined
25+
}
26+
const Actions = ({ ctx, plo, tabId }: IQuickLinkListProps) => {
27+
28+
return (
29+
ctx && plo ?
30+
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto} styles={scrollablePaneStyles}>
31+
<Separator alignContent="start" styles={separatorStyles}>Page actions</Separator>
32+
<ChangePageLayout ctx={ctx} plo={plo} tabId={tabId} />
33+
</ScrollablePane>
34+
: <></>
35+
)
36+
}
37+
38+
export default Actions
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { Checkbox, ChoiceGroup, CommandBarButton, DefaultButton, Dialog, DialogFooter, DialogType, IChoiceGroupOption, MessageBar, MessageBarType, PrimaryButton } from '@fluentui/react';
2+
import { useEffect, useState } from 'react';
3+
import { IQuickLinkListProps } from './Actions';
4+
import { buttonStyles } from './QuickLinkButton';
5+
6+
function updatePageLayout(siteUrl: string, serverRequestPath: string, layout: any) {
7+
console.log(siteUrl, serverRequestPath, layout);
8+
9+
fetch(siteUrl + '/_api/contextinfo', {
10+
method: 'POST',
11+
headers: {
12+
accept: 'application/json;odata=nometadata',
13+
'content-type': 'application/json;odata=nometadata',
14+
'X-ClientService-ClientTag': 'SPEDITOR'
15+
}
16+
})
17+
.then(response => response.json())
18+
.then((r) => {
19+
console.log(r);
20+
21+
fetch(siteUrl + "/_api/web/getFileByServerRelativeUrl('" + serverRequestPath + "')/listItemAllFields", {
22+
method: 'POST',
23+
headers: {
24+
accept: 'application/json;odata=nometadata',
25+
'X-HTTP-Method': 'MERGE',
26+
'IF-MATCH': '*',
27+
'X-RequestDigest': r.FormDigestValue,
28+
'content-type': 'application/json;odata=nometadata',
29+
'X-ClientService-ClientTag': 'SPEDITOR'
30+
},
31+
body: JSON.stringify({
32+
PageLayoutType: layout
33+
})
34+
}).then(() => {
35+
return true
36+
})
37+
});
38+
}
39+
40+
const ChangePageLayout = ({ plo, tabId, ctx }: IQuickLinkListProps) => {
41+
42+
const options: IChoiceGroupOption[] = [
43+
{ key: 'Home', text: 'Home' },
44+
{ key: 'Article', text: 'Article' },
45+
{ key: 'SingleWebPartAppPage', text: 'SingleWebPartAppPage' },
46+
{ key: 'RepostPage', text: 'RepostPage', disabled: true },
47+
{ key: 'HeaderlessSearchResults', text: 'HeaderlessSearchResults', disabled: true },
48+
{ key: 'Spaces', text: 'Spaces', disabled: true },
49+
{ key: 'Topic', text: 'Topic', disabled: true },
50+
];
51+
const modelProps = {
52+
isBlocking: false,
53+
styles: { main: { maxWidth: 450 } },
54+
};
55+
const dialogContentProps = {
56+
type: DialogType.largeHeader,
57+
title: 'Change pagelayout',
58+
subText: "Change the pagelayout of the current page. Enable `I know what I´m doing` for more pagelayouts.",
59+
};
60+
61+
const [hideDialog, setHideDialog] = useState(true);
62+
const [showDisabled, setShowDisabled] = useState(false);
63+
const [selected, setSelected] = useState(plo.PageLayoutType);
64+
const [showSuccess, setShowSuccess] = useState(false);
65+
66+
useEffect(() => {
67+
setTimeout(() => {
68+
setShowSuccess(false)
69+
}, 3000)
70+
}, [showSuccess])
71+
72+
73+
return (<>
74+
<CommandBarButton
75+
text={'Change pagelayout'}
76+
iconProps={{ iconName: 'Edit' }}
77+
styles={buttonStyles}
78+
disabled={!plo.PageLayoutType}
79+
onClick={() => setHideDialog(false)}
80+
/>
81+
<Dialog
82+
hidden={hideDialog}
83+
onDismiss={() => setHideDialog(false)}
84+
dialogContentProps={dialogContentProps}
85+
modalProps={modelProps}>
86+
<ChoiceGroup
87+
defaultSelectedKey={plo.PageLayoutType}
88+
options={options.map((e) => !showDisabled ? e : { ...e, disabled: false })}
89+
onChange={(e, option) => setSelected(option!.key)}
90+
/>
91+
<Checkbox styles={{ root: { marginTop: 10 } }}
92+
label="Enable more options please, I know what I'm doing!"
93+
onChange={(e, checked) => setShowDisabled(checked || false)}
94+
checked={showDisabled}
95+
/>
96+
{showSuccess ? <MessageBar messageBarType={MessageBarType.success}>
97+
Pagelayout changed, reload the page.
98+
</MessageBar> : <div style={{ height: 32 }}></div>}
99+
<DialogFooter>
100+
<PrimaryButton onClick={() => chrome.scripting.executeScript({
101+
target: { tabId: tabId },
102+
world: 'MAIN',
103+
args: [ctx.webAbsoluteUrl, ctx.serverRequestPath, selected],
104+
func: updatePageLayout,
105+
}).then(injectionResults => {
106+
console.log(injectionResults);
107+
setShowSuccess(true);
108+
})}
109+
text="Save"
110+
disabled={plo.PageLayoutType === selected} />
111+
<DefaultButton onClick={() => setHideDialog(true)} text="Close" />
112+
</DialogFooter>
113+
</Dialog>
114+
</>
115+
)
116+
}
117+
118+
export default ChangePageLayout;

0 commit comments

Comments
 (0)