feat: Entferne Auf-/Zuklapp-Schalter und aktualisiere Kartenstil im Organigramm; Version auf 1.1.1 erhöhen
This commit is contained in:
@@ -8,8 +8,7 @@ SPFx-1.4.1-WebPart für SharePoint Server Subscription Edition. Es erstellt aus
|
|||||||
- Startperson über einen SharePoint-People-Picker direkt im WebPart auswählen
|
- Startperson über einen SharePoint-People-Picker direkt im WebPart auswählen
|
||||||
- rekursives Laden aller darunterliegenden Ebenen über `DirectReports`
|
- rekursives Laden aller darunterliegenden Ebenen über `DirectReports`
|
||||||
- konfigurierbare maximale Tiefe und Personenzahl
|
- konfigurierbare maximale Tiefe und Personenzahl
|
||||||
- standardmäßig vollständig aufgeklappter Baum
|
- vollständig sichtbarer Baum ohne separate Auf-/Zuklapp-Schalter
|
||||||
- einzelne Zweige können auf- und zugeklappt werden
|
|
||||||
- Profilbild, Anzeigename, Position, Abteilung und optional Kontaktdaten
|
- Profilbild, Anzeigename, Position, Abteilung und optional Kontaktdaten
|
||||||
- Teams-ähnliche Personenkarten mit Text links und Profilbild rechts
|
- Teams-ähnliche Personenkarten mit Text links und Profilbild rechts
|
||||||
- aufklappbare Personendetails direkt in der Karte
|
- aufklappbare Personendetails direkt in der Karte
|
||||||
@@ -17,7 +16,8 @@ SPFx-1.4.1-WebPart für SharePoint Server Subscription Edition. Es erstellt aus
|
|||||||
- Verlinkung auf die SharePoint-Profilseite
|
- Verlinkung auf die SharePoint-Profilseite
|
||||||
- kurzzeitiger In-Memory-Profilcache zur Reduzierung wiederholter REST-Aufrufe
|
- kurzzeitiger In-Memory-Profilcache zur Reduzierung wiederholter REST-Aufrufe
|
||||||
- responsive Baumdarstellung mit automatisch kompakteren Karten bei breiten Organisationsebenen
|
- responsive Baumdarstellung mit automatisch kompakteren Karten bei breiten Organisationsebenen
|
||||||
- durchgehende Verbindungslinien und SharePoint-Chevrons zum Auf- und Zuklappen
|
- gerade Verbindungslinien zwischen den Hierarchieebenen
|
||||||
|
- schattenlose Karten im Teams-Stil
|
||||||
|
|
||||||
## Datenquelle und Voraussetzungen
|
## Datenquelle und Voraussetzungen
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"name": "organization-chart-webpart",
|
"name": "organization-chart-webpart",
|
||||||
"title": "organization-chart-webpart",
|
"title": "organization-chart-webpart",
|
||||||
"id": "b2f88ea8-f7da-4d0e-a945-6973bf1acd1e",
|
"id": "b2f88ea8-f7da-4d0e-a945-6973bf1acd1e",
|
||||||
"version": "1.1.0.0",
|
"version": "1.1.1.0",
|
||||||
"includeClientSideAssets": true,
|
"includeClientSideAssets": true,
|
||||||
"skipFeatureDeployment": true
|
"skipFeatureDeployment": true
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "organization-chart-webpart",
|
"name": "organization-chart-webpart",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"engines": { "node": ">=6.9.0 <9.0.0" },
|
"engines": { "node": ">=6.9.0 <9.0.0" },
|
||||||
|
|||||||
@@ -68,8 +68,6 @@ export default class OrganizationChartWebPart extends BaseClientSideWebPart<IOrg
|
|||||||
cardAccentColor: this.properties.cardAccentColor || '',
|
cardAccentColor: this.properties.cardAccentColor || '',
|
||||||
roundedCards: this.properties.roundedCards === true,
|
roundedCards: this.properties.roundedCards === true,
|
||||||
reportsLabel: strings.ReportsLabel,
|
reportsLabel: strings.ReportsLabel,
|
||||||
collapseLabel: strings.CollapseLabel,
|
|
||||||
expandLabel: strings.ExpandLabel,
|
|
||||||
expandDetailsLabel: strings.ExpandDetailsLabel,
|
expandDetailsLabel: strings.ExpandDetailsLabel,
|
||||||
collapseDetailsLabel: strings.CollapseDetailsLabel,
|
collapseDetailsLabel: strings.CollapseDetailsLabel,
|
||||||
departmentLabel: strings.DepartmentLabel,
|
departmentLabel: strings.DepartmentLabel,
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ export interface IOrganizationChartProps {
|
|||||||
cardAccentColor: string;
|
cardAccentColor: string;
|
||||||
roundedCards: boolean;
|
roundedCards: boolean;
|
||||||
reportsLabel: string;
|
reportsLabel: string;
|
||||||
collapseLabel: string;
|
|
||||||
expandLabel: string;
|
|
||||||
expandDetailsLabel: string;
|
expandDetailsLabel: string;
|
||||||
collapseDetailsLabel: string;
|
collapseDetailsLabel: string;
|
||||||
departmentLabel: string;
|
departmentLabel: string;
|
||||||
|
|||||||
@@ -22,14 +22,12 @@
|
|||||||
.tree li:only-child { padding-top: 0; }
|
.tree li:only-child { padding-top: 0; }
|
||||||
.tree > ul > li:only-child { padding-top: 0; }
|
.tree > ul > li:only-child { padding-top: 0; }
|
||||||
.tree li:first-child::before, .tree li:last-child::after { border: 0; }
|
.tree li:first-child::before, .tree li:last-child::after { border: 0; }
|
||||||
.tree li:last-child::before { border-right: 1px solid "[theme:neutralTertiary, default:#a19f9d]"; border-radius: 0 4px 0 0; }
|
.tree li:last-child::before { border-right: 1px solid "[theme:neutralTertiary, default:#a19f9d]"; }
|
||||||
.tree li:first-child::after { border-radius: 4px 0 0 0; }
|
|
||||||
.tree ul ul::before { border-left: 1px solid "[theme:neutralTertiary, default:#a19f9d]"; content: ''; height: 22px; left: 50%; position: absolute; top: 0; }
|
.tree ul ul::before { border-left: 1px solid "[theme:neutralTertiary, default:#a19f9d]"; content: ''; height: 22px; left: 50%; position: absolute; top: 0; }
|
||||||
.nodeWrap { display: inline-block; position: relative; }
|
.nodeWrap { display: inline-block; position: relative; }
|
||||||
.personCard {
|
.personCard {
|
||||||
background: "[theme:white, default:#ffffff]";
|
background: "[theme:white, default:#ffffff]";
|
||||||
border: 1px solid "[theme:neutralLight, default:#edebe9]";
|
border: 1px solid "[theme:neutralLight, default:#edebe9]";
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .12);
|
|
||||||
color: "[theme:bodyText, default:#323130]";
|
color: "[theme:bodyText, default:#323130]";
|
||||||
display: block;
|
display: block;
|
||||||
border-bottom: 2px solid "[theme:themePrimary, default:#0078d4]";
|
border-bottom: 2px solid "[theme:themePrimary, default:#0078d4]";
|
||||||
@@ -39,7 +37,7 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
width: 230px;
|
width: 230px;
|
||||||
}
|
}
|
||||||
.personCard:hover { box-shadow: 0 2px 6px rgba(0, 0, 0, .18); color: "[theme:bodyText, default:#323130]"; }
|
.personCard:hover { color: "[theme:bodyText, default:#323130]"; }
|
||||||
.cardContent { align-items: center; background: transparent; border: 0; color: inherit; cursor: pointer; display: flex; font-family: inherit; min-height: 92px; padding: 12px; position: relative; text-align: left; width: 100%; }
|
.cardContent { align-items: center; background: transparent; border: 0; color: inherit; cursor: pointer; display: flex; font-family: inherit; min-height: 92px; padding: 12px; position: relative; text-align: left; width: 100%; }
|
||||||
.cardContent:hover { background: rgba(0, 0, 0, .03); }
|
.cardContent:hover { background: rgba(0, 0, 0, .03); }
|
||||||
.cardContent:focus { outline: 2px solid "[theme:focusBorder, default:#605e5c]"; outline-offset: -2px; }
|
.cardContent:focus { outline: 2px solid "[theme:focusBorder, default:#605e5c]"; outline-offset: -2px; }
|
||||||
@@ -56,9 +54,6 @@
|
|||||||
.detailLabel { color: "[theme:neutralSecondary, default:#605e5c]"; display: block; font-size: 10px; line-height: 13px; }
|
.detailLabel { color: "[theme:neutralSecondary, default:#605e5c]"; display: block; font-size: 10px; line-height: 13px; }
|
||||||
.profileLink { color: "[theme:themePrimary, default:#0078d4]"; display: inline-block; font-size: 12px; margin-top: 3px; text-decoration: none; }
|
.profileLink { color: "[theme:themePrimary, default:#0078d4]"; display: inline-block; font-size: 12px; margin-top: 3px; text-decoration: none; }
|
||||||
.profileLink:hover { text-decoration: underline; }
|
.profileLink:hover { text-decoration: underline; }
|
||||||
.toggle { align-items: center; background: "[theme:white, default:#ffffff]"; border: 1px solid "[theme:neutralTertiary, default:#a19f9d]"; border-radius: 50%; bottom: -14px; color: "[theme:themePrimary, default:#0078d4]"; cursor: pointer; display: flex; height: 28px; justify-content: center; left: calc(50% - 14px); padding: 0; position: absolute; width: 28px; z-index: 2; }
|
|
||||||
.toggle:hover, .toggle:focus { background: "[theme:neutralLighter, default:#f3f2f1]"; outline: none; }
|
|
||||||
.toggleIcon { font-family: 'FabricMDL2Icons'; font-size: 12px; line-height: 1; }
|
|
||||||
.status { color: "[theme:bodyText, default:#323130]"; padding: 16px; }
|
.status { color: "[theme:bodyText, default:#323130]"; padding: 16px; }
|
||||||
.error { color: "[theme:errorText, default:#a80000]"; padding: 16px; }
|
.error { color: "[theme:errorText, default:#a80000]"; padding: 16px; }
|
||||||
|
|
||||||
@@ -87,5 +82,4 @@
|
|||||||
.tree li::before, .tree li::after, .tree ul ul::before { display: none; }
|
.tree li::before, .tree li::after, .tree ul ul::before { display: none; }
|
||||||
.tree > ul > li, .tree li:only-child { padding-top: 0; }
|
.tree > ul > li, .tree li:only-child { padding-top: 0; }
|
||||||
.personCard { max-width: 100%; width: 260px; }
|
.personCard { max-width: 100%; width: 260px; }
|
||||||
.toggle { bottom: auto; left: -14px; top: 34px; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import styles from './OrganizationChart.module.scss';
|
|||||||
import { IOrganizationChartProps } from './IOrganizationChartProps';
|
import { IOrganizationChartProps } from './IOrganizationChartProps';
|
||||||
|
|
||||||
export interface IOrganizationChartState {
|
export interface IOrganizationChartState {
|
||||||
collapsed: { [account: string]: boolean };
|
|
||||||
expandedDetails: { [account: string]: boolean };
|
expandedDetails: { [account: string]: boolean };
|
||||||
cardWidth: number;
|
cardWidth: number;
|
||||||
}
|
}
|
||||||
@@ -15,7 +14,7 @@ export default class OrganizationChart extends React.Component<IOrganizationChar
|
|||||||
|
|
||||||
public constructor(props: IOrganizationChartProps) {
|
public constructor(props: IOrganizationChartProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { collapsed: {}, expandedDetails: {}, cardWidth: 230 };
|
this.state = { expandedDetails: {}, cardWidth: 230 };
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount(): void {
|
public componentDidMount(): void {
|
||||||
@@ -55,24 +54,14 @@ export default class OrganizationChart extends React.Component<IOrganizationChar
|
|||||||
|
|
||||||
private renderNode(node: IOrganizationNode, level: number): JSX.Element {
|
private renderNode(node: IOrganizationNode, level: number): JSX.Element {
|
||||||
const profile: IUserProfile = node.profile;
|
const profile: IUserProfile = node.profile;
|
||||||
const key: string = profile.accountName.toLowerCase();
|
|
||||||
const collapsed: boolean = this.state.collapsed[key] === true;
|
|
||||||
const hasChildren: boolean = node.children.length > 0;
|
const hasChildren: boolean = node.children.length > 0;
|
||||||
return (
|
return (
|
||||||
<li key={profile.accountName} role='treeitem' aria-level={level}
|
<li key={profile.accountName} role='treeitem' aria-level={level}
|
||||||
aria-selected={false} aria-expanded={hasChildren ? !collapsed : undefined}>
|
aria-selected={false}>
|
||||||
<div className={styles.nodeWrap}>
|
<div className={styles.nodeWrap}>
|
||||||
{this.renderPerson(profile)}
|
{this.renderPerson(profile)}
|
||||||
{hasChildren && <button type='button' className={styles.toggle}
|
|
||||||
title={collapsed ? this.props.expandLabel : this.props.collapseLabel}
|
|
||||||
aria-label={(collapsed ? this.props.expandLabel : this.props.collapseLabel) + ': ' + profile.displayName}
|
|
||||||
aria-expanded={!collapsed} onClick={(): void => this.toggle(key)}>
|
|
||||||
{collapsed
|
|
||||||
? <span className={styles.toggleIcon} aria-hidden='true'></span>
|
|
||||||
: <span className={styles.toggleIcon} aria-hidden='true'></span>}
|
|
||||||
</button>}
|
|
||||||
</div>
|
</div>
|
||||||
{hasChildren && !collapsed &&
|
{hasChildren &&
|
||||||
<ul role='group'>
|
<ul role='group'>
|
||||||
{node.children.map((child: IOrganizationNode): JSX.Element => this.renderNode(child, level + 1))}
|
{node.children.map((child: IOrganizationNode): JSX.Element => this.renderNode(child, level + 1))}
|
||||||
</ul>}
|
</ul>}
|
||||||
@@ -208,23 +197,11 @@ export default class OrganizationChart extends React.Component<IOrganizationChar
|
|||||||
|
|
||||||
private countVisibleNodes(node: IOrganizationNode, level: number, countsByLevel: number[]): void {
|
private countVisibleNodes(node: IOrganizationNode, level: number, countsByLevel: number[]): void {
|
||||||
countsByLevel[level] = (countsByLevel[level] || 0) + 1;
|
countsByLevel[level] = (countsByLevel[level] || 0) + 1;
|
||||||
const key: string = node.profile.accountName.toLowerCase();
|
|
||||||
if (this.state.collapsed[key] === true) { return; }
|
|
||||||
for (let i: number = 0; i < node.children.length; i++) {
|
for (let i: number = 0; i < node.children.length; i++) {
|
||||||
this.countVisibleNodes(node.children[i], level + 1, countsByLevel);
|
this.countVisibleNodes(node.children[i], level + 1, countsByLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private toggle(key: string): void {
|
|
||||||
const collapsed: { [account: string]: boolean } = {};
|
|
||||||
const existingKeys: string[] = Object.keys(this.state.collapsed);
|
|
||||||
for (let i: number = 0; i < existingKeys.length; i++) {
|
|
||||||
collapsed[existingKeys[i]] = this.state.collapsed[existingKeys[i]];
|
|
||||||
}
|
|
||||||
collapsed[key] = !collapsed[key];
|
|
||||||
this.setState({ collapsed: collapsed });
|
|
||||||
}
|
|
||||||
|
|
||||||
private initials(displayName: string): string {
|
private initials(displayName: string): string {
|
||||||
const parts: string[] = String(displayName || '?').trim().split(/\s+/);
|
const parts: string[] = String(displayName || '?').trim().split(/\s+/);
|
||||||
return (parts[0].charAt(0) + (parts.length > 1 ? parts[parts.length - 1].charAt(0) : '')).toUpperCase();
|
return (parts[0].charAt(0) + (parts.length > 1 ? parts[parts.length - 1].charAt(0) : '')).toUpperCase();
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ define([], function() {
|
|||||||
LoadErrorMessage: 'Das Organigramm konnte nicht aus dem User Profile Service geladen werden.',
|
LoadErrorMessage: 'Das Organigramm konnte nicht aus dem User Profile Service geladen werden.',
|
||||||
DefaultTitle: 'Organigramm',
|
DefaultTitle: 'Organigramm',
|
||||||
ReportsLabel: 'Mitarbeiter',
|
ReportsLabel: 'Mitarbeiter',
|
||||||
CollapseLabel: 'Untergeordnete Personen zuklappen',
|
|
||||||
ExpandLabel: 'Untergeordnete Personen aufklappen',
|
|
||||||
ExpandDetailsLabel: 'Personendetails anzeigen',
|
ExpandDetailsLabel: 'Personendetails anzeigen',
|
||||||
CollapseDetailsLabel: 'Personendetails ausblenden',
|
CollapseDetailsLabel: 'Personendetails ausblenden',
|
||||||
DepartmentLabel: 'Abteilung',
|
DepartmentLabel: 'Abteilung',
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ define([], function() {
|
|||||||
LoadErrorMessage: 'The organization chart could not be loaded from the User Profile Service.',
|
LoadErrorMessage: 'The organization chart could not be loaded from the User Profile Service.',
|
||||||
DefaultTitle: 'Organization Chart',
|
DefaultTitle: 'Organization Chart',
|
||||||
ReportsLabel: 'reports',
|
ReportsLabel: 'reports',
|
||||||
CollapseLabel: 'Collapse reports',
|
|
||||||
ExpandLabel: 'Expand reports',
|
|
||||||
ExpandDetailsLabel: 'Show person details',
|
ExpandDetailsLabel: 'Show person details',
|
||||||
CollapseDetailsLabel: 'Hide person details',
|
CollapseDetailsLabel: 'Hide person details',
|
||||||
DepartmentLabel: 'Department',
|
DepartmentLabel: 'Department',
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ declare interface IOrganizationChartWebPartStrings {
|
|||||||
LoadErrorMessage: string;
|
LoadErrorMessage: string;
|
||||||
DefaultTitle: string;
|
DefaultTitle: string;
|
||||||
ReportsLabel: string;
|
ReportsLabel: string;
|
||||||
CollapseLabel: string;
|
|
||||||
ExpandLabel: string;
|
|
||||||
ExpandDetailsLabel: string;
|
ExpandDetailsLabel: string;
|
||||||
CollapseDetailsLabel: string;
|
CollapseDetailsLabel: string;
|
||||||
DepartmentLabel: string;
|
DepartmentLabel: string;
|
||||||
|
|||||||
@@ -44,7 +44,11 @@ assert(component.indexOf("role='tree'") >= 0 && component.indexOf("role='treeite
|
|||||||
assert(component.indexOf('aria-expanded') >= 0 && component.indexOf('aria-selected={false}') >= 0,
|
assert(component.indexOf('aria-expanded') >= 0 && component.indexOf('aria-selected={false}') >= 0,
|
||||||
'Accessible tree state is missing.');
|
'Accessible tree state is missing.');
|
||||||
assert(component.indexOf('peopleCountLabel') < 0 && component.indexOf('') >= 0 &&
|
assert(component.indexOf('peopleCountLabel') < 0 && component.indexOf('') >= 0 &&
|
||||||
component.indexOf('') >= 0, 'Header count removal or modern toggle icons are missing.');
|
component.indexOf('') >= 0, 'Header count removal or detail chevrons are missing.');
|
||||||
|
assert(component.indexOf('styles.toggle') < 0 && styles.indexOf('.toggle {') < 0 &&
|
||||||
|
component.indexOf('collapsed') < 0, 'Branch toggle circles must not be rendered.');
|
||||||
|
assert(styles.indexOf('box-shadow') < 0 && styles.indexOf('border-radius: 0 4px') < 0,
|
||||||
|
'Cards must be shadowless and connectors must use straight corners.');
|
||||||
assert(styles.indexOf('.tree li:only-child { padding-top: 0; }') >= 0,
|
assert(styles.indexOf('.tree li:only-child { padding-top: 0; }') >= 0,
|
||||||
'Single-child connector spacing must remain continuous.');
|
'Single-child connector spacing must remain continuous.');
|
||||||
assert(component.indexOf('getMaximumVisibleBreadth') >= 0 && component.indexOf('getBreadthCardWidth') >= 0 &&
|
assert(component.indexOf('getMaximumVisibleBreadth') >= 0 && component.indexOf('getBreadthCardWidth') >= 0 &&
|
||||||
|
|||||||
Reference in New Issue
Block a user