Browse Source

Merge branch 'next' of github.com:Squidex/squidex into next

pull/372/head
Sebastian Stehle 7 years ago
parent
commit
31a666dea1
  1. 36
      src/Squidex/.vscode/settings.json
  2. 25
      src/Squidex/app-config/helpers.js
  3. 3
      src/Squidex/app-config/karma-test-shim.js
  4. 6
      src/Squidex/app-config/karma.conf.js
  5. 6
      src/Squidex/app-config/karma.coverage.conf.js
  6. 272
      src/Squidex/app-config/webpack.config.js
  7. 35
      src/Squidex/app-config/webpack.run.base.js
  8. 68
      src/Squidex/app-config/webpack.run.dev.js
  9. 137
      src/Squidex/app-config/webpack.run.prod.js
  10. 37
      src/Squidex/app-config/webpack.test.coverage.js
  11. 16
      src/Squidex/app-config/webpack.test.js
  12. 18
      src/Squidex/app/app.routes.ts
  13. 2
      src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts
  14. 2
      src/Squidex/app/features/apps/pages/apps-page.component.scss
  15. 2
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  16. 2
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  17. 4
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.html
  18. 10
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts
  19. 4
      src/Squidex/app/features/rules/pages/rules/rule-element.component.scss
  20. 2
      src/Squidex/app/features/schemas/pages/schema/field-wizard.component.ts
  21. 5
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html
  22. 5
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
  23. 2
      src/Squidex/app/features/settings/pages/roles/role.component.ts
  24. 4
      src/Squidex/app/framework/angular/forms/autocomplete.component.ts
  25. 2
      src/Squidex/app/framework/angular/forms/code-editor.component.ts
  26. 2
      src/Squidex/app/framework/angular/forms/date-time-editor.component.ts
  27. 2
      src/Squidex/app/framework/angular/forms/iframe-editor.component.ts
  28. 2
      src/Squidex/app/framework/angular/forms/json-editor.component.ts
  29. 4
      src/Squidex/app/framework/angular/forms/tag-editor.component.ts
  30. 4
      src/Squidex/app/framework/angular/modals/modal-dialog.component.ts
  31. 2
      src/Squidex/app/framework/angular/modals/root-view.component.ts
  32. 2
      src/Squidex/app/framework/angular/panel.component.ts
  33. 4
      src/Squidex/app/shared/components/geolocation-editor.component.ts
  34. 6
      src/Squidex/app/shared/components/markdown-editor.component.ts
  35. 2
      src/Squidex/app/shared/components/rich-editor.component.ts
  36. 20
      src/Squidex/app/shared/components/schema-category.component.html
  37. 68
      src/Squidex/app/shared/components/schema-category.component.ts
  38. 59
      src/Squidex/app/shared/state/schemas.state.spec.ts
  39. 71
      src/Squidex/app/shared/state/schemas.state.ts
  40. 2
      src/Squidex/app/shell/pages/internal/internal-area.component.scss
  41. 103
      src/Squidex/app/shims.ts
  42. 2
      src/Squidex/app/theme/_bootstrap.scss
  43. 2767
      src/Squidex/package-lock.json
  44. 81
      src/Squidex/package.json
  45. 1
      src/Squidex/tsconfig.json

36
src/Squidex/.vscode/settings.json

@ -1,36 +0,0 @@
{
// When opening a file, `editor.tabSize` and `editor.insertSpaces` will be detected based on the file contents.
"editor.detectIndentation": false,
// Typescript version from local package to be consistent
"typescript.tsdk": "node_modules/typescript/lib",
// Configure glob patterns for excluding files and folders.
"files.exclude": {
"**/node_modules": true,
"**/Assets": true,
"**/artifacts": true,
"**/build": true,
"**/logs": true,
"**/out": true,
"**/obj": true,
"**/bin": true,
"**/*.lock.json": true,
"**/*.bat": true,
"**/*.sln": true,
"**/*.sln.DotSettings": true,
"**/*.user": true,
"**/*.xproj": true,
"**/*.gitattributes": true,
"appsetttings.Development.json": true,
"appsetttings.Production.json": true,
".awcache": true,
".vs:": true,
".vscode:": true
},
"coverage-gutters.coverageFileNames": [
"_test-output/coverage/lcov.info"
],
"coverage-gutters.showLineCoverage": true
}

25
src/Squidex/app-config/helpers.js

@ -1,25 +0,0 @@
var path = require('path');
var appRoot = path.resolve(__dirname, '..');
exports.root = function () {
var newArgs = Array.prototype.slice.call(arguments, 0);
return path.join.apply(path, [appRoot].concat(newArgs));
};
exports.removeLoaders = function (config, extensions) {
var rules = config.module.rules;
for (var i = 0; i < rules.length; i += 1) {
var rule = rules[i];
for (var j = 0; j < extensions.length; j += 1) {
if (rule.test.source.indexOf(extensions[j]) >= 0) {
rules.splice(i, 1);
i--;
break;
}
}
}
}

3
src/Squidex/app-config/karma-test-shim.js

@ -1,7 +1,6 @@
Error.stackTraceLimit = Infinity; Error.stackTraceLimit = Infinity;
require('core-js/es6'); require('core-js/proposals/reflect-metadata');
require('core-js/es7/reflect');
require('zone.js/dist/zone'); require('zone.js/dist/zone');
require('zone.js/dist/long-stack-trace-zone'); require('zone.js/dist/long-stack-trace-zone');

6
src/Squidex/app-config/karma.conf.js

@ -1,4 +1,4 @@
var webpackConfig = require('./webpack.test'); const webpackConfig = require('./webpack.config');
module.exports = function (config) { module.exports = function (config) {
var _config = { var _config = {
@ -10,7 +10,7 @@ module.exports = function (config) {
frameworks: ['jasmine'], frameworks: ['jasmine'],
/** /**
* Load additional test shim to setup angular2 for testing. * Load additional test shim to setup angular for testing.
*/ */
files: [ files: [
{ pattern: './app-config/karma-test-shim.js', watched: false } { pattern: './app-config/karma-test-shim.js', watched: false }
@ -23,7 +23,7 @@ module.exports = function (config) {
/** /**
* Load the files with webpack and use test configuration for it. * Load the files with webpack and use test configuration for it.
*/ */
webpack: webpackConfig, webpack: webpackConfig({ target: 'tests', jit: true }),
webpackMiddleware: { webpackMiddleware: {
stats: 'errors-only' stats: 'errors-only'

6
src/Squidex/app-config/karma.coverage.conf.js

@ -1,4 +1,4 @@
var webpackConfig = require('./webpack.test.coverage'); const webpackConfig = require('./webpack.config');
module.exports = function (config) { module.exports = function (config) {
var _config = { var _config = {
@ -10,7 +10,7 @@ module.exports = function (config) {
frameworks: ['jasmine'], frameworks: ['jasmine'],
/** /**
* Load additional test shim to setup angular2 for testing. * Load additional test shim to setup angular for testing.
*/ */
files: [ files: [
{ pattern: './app-config/karma-test-shim.js', watched: false } { pattern: './app-config/karma-test-shim.js', watched: false }
@ -23,7 +23,7 @@ module.exports = function (config) {
/** /**
* Load the files with webpack and use test configuration for it. * Load the files with webpack and use test configuration for it.
*/ */
webpack: webpackConfig, webpack: webpackConfig({ target: 'tests', coverage: true, jit: true }),
webpackMiddleware: { webpackMiddleware: {
stats: 'errors-only' stats: 'errors-only'

272
src/Squidex/app-config/webpack.config.js

@ -1,6 +1,13 @@
const webpack = require('webpack'), const webpack = require('webpack'),
path = require('path'), path = require('path');
helpers = require('./helpers');
const appRoot = path.resolve(__dirname, '..');
function root() {
var newArgs = Array.prototype.slice.call(arguments, 0);
return path.join.apply(path, [appRoot].concat(newArgs));
};
const plugins = { const plugins = {
// https://github.com/webpack-contrib/mini-css-extract-plugin // https://github.com/webpack-contrib/mini-css-extract-plugin
@ -8,12 +15,36 @@ const plugins = {
// https://github.com/dividab/tsconfig-paths-webpack-plugin // https://github.com/dividab/tsconfig-paths-webpack-plugin
TsconfigPathsPlugin: require('tsconfig-paths-webpack-plugin'), TsconfigPathsPlugin: require('tsconfig-paths-webpack-plugin'),
// https://github.com/aackerman/circular-dependency-plugin // https://github.com/aackerman/circular-dependency-plugin
CircularDependencyPlugin: require('circular-dependency-plugin') CircularDependencyPlugin: require('circular-dependency-plugin'),
// https://github.com/jantimon/html-webpack-plugin
HtmlWebpackPlugin: require('html-webpack-plugin'),
// https://github.com/mishoo/UglifyJS2/tree/harmony
UglifyJsPlugin: require('uglifyjs-webpack-plugin'),
// https://www.npmjs.com/package/@ngtools/webpack
NgToolsWebpack: require('@ngtools/webpack'),
// https://github.com/NMFR/optimize-css-assets-webpack-plugin
OptimizeCSSAssetsPlugin: require("optimize-css-assets-webpack-plugin"),
// https://github.com/jrparish/tslint-webpack-plugin
TsLintPlugin: require('tslint-webpack-plugin')
}; };
const isDevServer = path.basename(require.main.filename) === 'webpack-dev-server.js'; module.exports = function(env) {
const isDevServer = path.basename(require.main.filename) === 'webpack-dev-server.js';
const isProduction = env && env.production;
const isTesting = env && env.target === 'tests';
const isCoverage = env && env.coverage;
const isJit = env && env.jit;
const config = {
mode: isProduction ? 'production' : 'development',
/**
* Source map for Karma from the help of karma-sourcemap-loader & karma-webpack.
*
* See: https://webpack.js.org/configuration/devtool/
*/
devtool: isProduction ? undefined : (isTesting ? 'inline-source-map' : 'source-map'),
module.exports = {
/** /**
* Options affecting the resolving of modules. * Options affecting the resolving of modules.
* *
@ -27,9 +58,9 @@ module.exports = {
*/ */
extensions: ['.js', '.mjs', '.ts', '.css', '.scss'], extensions: ['.js', '.mjs', '.ts', '.css', '.scss'],
modules: [ modules: [
helpers.root('app'), root('app'),
helpers.root('app', 'theme'), root('app', 'theme'),
helpers.root('node_modules') root('node_modules')
], ],
plugins: [ plugins: [
@ -56,22 +87,6 @@ module.exports = {
test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/, test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/,
parser: { system: true }, parser: { system: true },
include: [/node_modules/] include: [/node_modules/]
}, {
test: /\.ts$/,
use: [{
loader: 'awesome-typescript-loader'
}, {
loader: 'angular-router-loader'
}, {
loader: 'angular2-template-loader'
}],
exclude: [/node_modules/]
}, {
test: /\.ts$/,
use: [{
loader: 'awesome-typescript-loader'
}],
include: [/node_modules/]
}, { }, {
test: /\.js\.flow$/, test: /\.js\.flow$/,
use: [{ use: [{
@ -115,13 +130,16 @@ module.exports = {
use: [{ use: [{
loader: 'raw-loader' loader: 'raw-loader'
}, { }, {
loader: 'sass-loader', options: { includePaths: [helpers.root('app', 'theme')] } loader: 'sass-loader', options: { includePaths: [root('app', 'theme')] }
}], }],
exclude: helpers.root('app', 'theme') exclude: root('app', 'theme')
}] }]
}, },
plugins: [ plugins: [
new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)fesm5/, root('./app'), {}),
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
/** /**
* Puts each bundle into a file and appends the hash of the file to the path. * Puts each bundle into a file and appends the hash of the file to the path.
* *
@ -137,18 +155,216 @@ module.exports = {
* *
* See: https://github.com/webpack/html-loader#Advanced_Options * See: https://github.com/webpack/html-loader#Advanced_Options
*/ */
root: helpers.root('app', 'images') root: root('app', 'images')
}, },
context: '/' context: '/'
} }
}), }),
/**
* Detect circular dependencies in app.
*
* See: https://github.com/aackerman/circular-dependency-plugin
*/
new plugins.CircularDependencyPlugin({ new plugins.CircularDependencyPlugin({
exclude: /([\\\/]node_modules[\\\/])|(ngfactory\.js$)/, exclude: /([\\\/]node_modules[\\\/])|(ngfactory\.js$)/,
// Add errors to webpack instead of warnings // Add errors to webpack instead of warnings
failOnError: true failOnError: true
}), }),
],
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/) devServer: {
headers: {
'Access-Control-Allow-Origin': '*'
},
historyApiFallback: true
}
};
if (!isTesting) {
/**
* The entry point for the bundle. Our Angular app.
*
* See: https://webpack.js.org/configuration/entry-context/
*/
config.entry = {
'shims': './app/shims.ts',
'app': './app/app.ts'
};
if (isProduction) {
config.output = {
/**
* The output directory as absolute path (required).
*
* See: https://webpack.js.org/configuration/output/#output-path
*/
path: root('wwwroot/build/'),
publicPath: './build/',
/**
* Specifies the name of each output file on disk.
*
* See: https://webpack.js.org/configuration/output/#output-filename
*/
filename: '[name].js',
/**
* The filename of non-entry chunks as relative path inside the output.path directory.
*
* See: https://webpack.js.org/configuration/output/#output-chunkfilename
*/
chunkFilename: '[id].[hash].chunk.js'
};
} else {
config.output = {
filename: '[name].js',
/**
* Set the public path, because we are running the website from another port (5000).
*/
publicPath: 'http://localhost:3000/'
};
}
config.plugins.push(
new plugins.HtmlWebpackPlugin({
hash: true,
chunks: ['shims', 'app'],
chunksSortMode: 'manual',
template: 'wwwroot/index.html'
})
);
config.plugins.push(
new plugins.HtmlWebpackPlugin({
template: 'wwwroot/_theme.html', hash: true, chunksSortMode: 'none', filename: 'theme.html'
})
);
config.plugins.push(
new plugins.TsLintPlugin({
files: ['./app/**/*.ts'],
/**
* Path to a configuration file.
*/
config: root('tslint.json'),
/**
* Wait for linting and fail the build when linting error occur.
*/
waitForLinting: isProduction
})
);
}
if (isProduction) {
config.optimization = {
minimizer: [
new plugins.UglifyJsPlugin({
uglifyOptions: {
compress: false,
ecma: 6,
mangle: true,
output: {
comments: false
}
},
extractComments: true
}),
new plugins.OptimizeCSSAssetsPlugin({})
] ]
};
config.performance = {
hints: false
};
}
if (!isCoverage) {
config.module.rules.push({
test: /\.ts$/,
use: [{
loader: 'awesome-typescript-loader'
}],
exclude: [/node_modules/]
})
} else {
config.module.rules.push({
test: /\.ts$/,
use: [{
loader: 'ts-loader'
}],
include: [/\.(e2e|spec)\.ts$/],
});
// Use instrument loader for all normal builds.
config.module.rules.push({
test: /\.ts$/,
use: [{
loader: 'istanbul-instrumenter-loader'
}, {
loader: 'ts-loader'
}],
exclude: [/\.(e2e|spec)\.ts$/]
});
}
if (isProduction) {
config.module.rules.push({
test: /\.scss$/,
/*
* Extract the content from a bundle to a file.
*
* See: https://github.com/webpack-contrib/extract-text-webpack-plugin
*/
use: [
plugins.MiniCssExtractPlugin.loader,
{
loader: 'css-loader'
}, {
loader: 'sass-loader'
}],
/*
* Do not include component styles.
*/
include: root('app', 'theme'),
});
} else {
config.module.rules.push({
test: /\.scss$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader'
}, {
loader: 'sass-loader?sourceMap'
}],
/*
* Do not include component styles.
*/
include: root('app', 'theme')
});
}
if (!isJit) {
config.module.rules.push({
test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
use: [{
loader: '@ngtools/webpack'
}]
});
config.plugins.push(
new plugins.NgToolsWebpack.AngularCompilerPlugin({
entryModule: 'app/app.module#AppModule',
sourceMap: !isProduction,
skipSourceGeneration: false,
tsConfigPath: './tsconfig.json'
})
);
}
return config;
}; };

35
src/Squidex/app-config/webpack.run.base.js

@ -1,35 +0,0 @@
const webpack = require('webpack'),
webpackMerge = require('webpack-merge'),
path = require('path'),
helpers = require('./helpers'),
commonConfig = require('./webpack.config.js');
const plugins = {
// https://github.com/jantimon/html-webpack-plugin
HtmlWebpackPlugin: require('html-webpack-plugin')
};
module.exports = webpackMerge(commonConfig, {
/**
* The entry point for the bundle. Our Angular app.
*
* See: https://webpack.js.org/configuration/entry-context/
*/
entry: {
'shims': './app/shims.ts',
'app': './app/app.ts'
},
plugins: [
new plugins.HtmlWebpackPlugin({
hash: true,
chunks: ['shims', 'app'],
chunksSortMode: 'manual',
template: 'wwwroot/index.html'
}),
new plugins.HtmlWebpackPlugin({
template: 'wwwroot/_theme.html', hash: true, chunksSortMode: 'none', filename: 'theme.html'
})
]
});

68
src/Squidex/app-config/webpack.run.dev.js

@ -1,68 +0,0 @@
const webpack = require('webpack'),
webpackMerge = require('webpack-merge'),
path = require('path'),
helpers = require('./helpers'),
runConfig = require('./webpack.run.base.js');
const plugins = {
// https://github.com/jrparish/tslint-webpack-plugin
TsLintPlugin: require('tslint-webpack-plugin')
};
module.exports = webpackMerge(runConfig, {
mode: 'development',
devtool: 'source-map',
output: {
filename: '[name].js',
/**
* Set the public path, because we are running the website from another port (5000).
*/
publicPath: 'http://localhost:3000/'
},
/*
* Options affecting the normal modules.
*
* See: https://webpack.js.org/configuration/module/
*/
module: {
/**
* An array of Rules which are matched to requests when modules are created.
*
* See: https://webpack.js.org/configuration/module/#module-rules
*/
rules: [{
test: /\.scss$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader'
}, {
loader: 'sass-loader?sourceMap', options: { includePaths: [helpers.root('app', 'theme')] }
}],
include: helpers.root('app', 'theme')
}]
},
plugins: [
new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)fesm5/, helpers.root('./src'), {}),
new plugins.TsLintPlugin({
files: ['./app/**/*.ts'],
/**
* Path to a configuration file.
*/
config: helpers.root('tslint.json')
})
],
devServer: {
headers: {
'Access-Control-Allow-Origin': '*'
},
historyApiFallback: true
}
});

137
src/Squidex/app-config/webpack.run.prod.js

@ -1,137 +0,0 @@
const webpack = require('webpack'),
webpackMerge = require('webpack-merge'),
path = require('path'),
helpers = require('./helpers'),
runConfig = require('./webpack.run.base.js');
const plugins = {
// https://github.com/mishoo/UglifyJS2/tree/harmony
UglifyJsPlugin: require('uglifyjs-webpack-plugin'),
// https://www.npmjs.com/package/@ngtools/webpack
NgToolsWebpack: require('@ngtools/webpack'),
// https://github.com/webpack-contrib/mini-css-extract-plugin
MiniCssExtractPlugin: require('mini-css-extract-plugin'),
// https://github.com/NMFR/optimize-css-assets-webpack-plugin
OptimizeCSSAssetsPlugin: require("optimize-css-assets-webpack-plugin"),
// https://github.com/jrparish/tslint-webpack-plugin
TsLintPlugin: require('tslint-webpack-plugin')
};
helpers.removeLoaders(runConfig, ['scss', 'ts']);
module.exports = webpackMerge(runConfig, {
mode: 'production',
output: {
/**
* The output directory as absolute path (required).
*
* See: https://webpack.js.org/configuration/output/#output-path
*/
path: helpers.root('wwwroot/build/'),
publicPath: './build/',
/**
* Specifies the name of each output file on disk.
*
* See: https://webpack.js.org/configuration/output/#output-filename
*/
filename: '[name].js',
/**
* The filename of non-entry chunks as relative path inside the output.path directory.
*
* See: https://webpack.js.org/configuration/output/#output-chunkfilename
*/
chunkFilename: '[id].[hash].chunk.js'
},
/*
* Options affecting the normal modules.
*
* See: https://webpack.js.org/configuration/module/
*/
module: {
/**
* An array of Rules which are matched to requests when modules are created.
*
* See: https://webpack.js.org/configuration/module/#module-rules
*/
rules: [{
test: /\.scss$/,
/*
* Extract the content from a bundle to a file.
*
* See: https://github.com/webpack-contrib/extract-text-webpack-plugin
*/
use: [
plugins.MiniCssExtractPlugin.loader,
{
loader: 'css-loader'
}, {
loader: 'sass-loader'
}],
/*
* Do not include component styles.
*/
include: helpers.root('app', 'theme'),
}, {
test: /\.scss$/,
use: [{
loader: 'raw-loader'
}, {
loader: 'sass-loader', options: { includePaths: [helpers.root('app', 'theme')] }
}],
exclude: helpers.root('app', 'theme'),
}, {
test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
use: [{
loader: '@ngtools/webpack'
}]
}]
},
plugins: [
new plugins.NgToolsWebpack.AngularCompilerPlugin({
entryModule: 'app/app.module#AppModule',
sourceMap: false,
skipSourceGeneration: false,
tsConfigPath: './tsconfig.json'
}),
new plugins.TsLintPlugin({
files: ['./app/**/*.ts'],
/**
* Path to a configuration file.
*/
config: helpers.root('tslint.json'),
/**
* Wait for linting and fail the build when linting error occur.
*/
waitForLinting: true
})
],
optimization: {
minimizer: [
new plugins.UglifyJsPlugin({
uglifyOptions: {
compress: false,
ecma: 6,
mangle: true,
output: {
comments: false
}
},
extractComments: true
}),
new plugins.OptimizeCSSAssetsPlugin({})
]
},
performance: {
hints: false
}
});

37
src/Squidex/app-config/webpack.test.coverage.js

@ -1,37 +0,0 @@
const webpack = require('webpack'),
webpackMerge = require('webpack-merge'),
path = require('path'),
helpers = require('./helpers'),
testConfig = require('./webpack.test.js');
helpers.removeLoaders(testConfig, ['ts']);
module.exports = webpackMerge(testConfig, {
module: {
/**
* An array of Rules which are matched to requests when modules are created.
*
* See: https://webpack.js.org/configuration/module/#module-rules
*/
rules: [{
test: /\.ts$/,
use: [{
loader: 'ts-loader'
}],
include: [/\.(e2e|spec)\.ts$/],
}, {
test: /\.ts$/,
use: [{
loader: 'istanbul-instrumenter-loader'
}, {
loader: 'ts-loader'
}, {
loader: 'angular-router-loader'
}, {
loader: 'angular2-template-loader'
}],
exclude: [/\.(e2e|spec)\.ts$/]
}]
}
});

16
src/Squidex/app-config/webpack.test.js

@ -1,16 +0,0 @@
const webpack = require('webpack'),
webpackMerge = require('webpack-merge'),
path = require('path'),
helpers = require('./helpers'),
commonConfig = require('./webpack.config.js');
module.exports = webpackMerge(commonConfig, {
mode: 'development',
/**
* Source map for Karma from the help of karma-sourcemap-loader & karma-webpack.
*
* See: https://webpack.js.org/configuration/devtool/
*/
devtool: 'inline-source-map'
});

18
src/Squidex/app/app.routes.ts

@ -38,12 +38,12 @@ export const routes: Routes = [
children: [ children: [
{ {
path: '', path: '',
loadChildren: './features/apps/module#SqxFeatureAppsModule', loadChildren: () => import('./features/apps/module').then(m => m.SqxFeatureAppsModule),
canActivate: [UnsetAppGuard] canActivate: [UnsetAppGuard]
}, },
{ {
path: 'administration', path: 'administration',
loadChildren: './features/administration/module#SqxFeatureAdministrationModule', loadChildren: () => import('./features/administration/module').then(m => m.SqxFeatureAdministrationModule),
canActivate: [UnsetAppGuard] canActivate: [UnsetAppGuard]
}, },
{ {
@ -53,31 +53,31 @@ export const routes: Routes = [
children: [ children: [
{ {
path: '', path: '',
loadChildren: './features/dashboard/module#SqxFeatureDashboardModule' loadChildren: () => import('./features/dashboard/module').then(m => m.SqxFeatureDashboardModule)
}, },
{ {
path: 'content', path: 'content',
loadChildren: './features/content/module#SqxFeatureContentModule' loadChildren: () => import('./features/content/module').then(m => m.SqxFeatureContentModule)
}, },
{ {
path: 'schemas', path: 'schemas',
loadChildren: './features/schemas/module#SqxFeatureSchemasModule' loadChildren: () => import('./features/schemas/module').then(m => m.SqxFeatureSchemasModule)
}, },
{ {
path: 'assets', path: 'assets',
loadChildren: './features/assets/module#SqxFeatureAssetsModule' loadChildren: () => import('./features/assets/module').then(m => m.SqxFeatureAssetsModule)
}, },
{ {
path: 'rules', path: 'rules',
loadChildren: './features/rules/module#SqxFeatureRulesModule' loadChildren: () => import('./features/rules/module').then(m => m.SqxFeatureRulesModule)
}, },
{ {
path: 'settings', path: 'settings',
loadChildren: './features/settings/module#SqxFeatureSettingsModule' loadChildren: () => import('./features/settings/module').then(m => m.SqxFeatureSettingsModule)
}, },
{ {
path: 'api', path: 'api',
loadChildren: './features/api/module#SqxFeatureApiModule' loadChildren: () => import('./features/api/module').then(m => m.SqxFeatureApiModule)
} }
] ]
} }

2
src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts

@ -22,7 +22,7 @@ import { AppsState, GraphQlService } from '@app/shared';
templateUrl: './graphql-page.component.html' templateUrl: './graphql-page.component.html'
}) })
export class GraphQLPageComponent implements AfterViewInit { export class GraphQLPageComponent implements AfterViewInit {
@ViewChild('graphiQLContainer') @ViewChild('graphiQLContainer', { static: false })
public graphiQLContainer: ElementRef; public graphiQLContainer: ElementRef;
constructor( constructor(

2
src/Squidex/app/features/apps/pages/apps-page.component.scss

@ -66,7 +66,7 @@
} }
&:hover { &:hover {
@include box-shadow(0, 3px, 16px, .2px); @include box-shadow(0, 3px, 16px, .2);
} }
&:focus { &:focus {

2
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -59,7 +59,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
public language: AppLanguageDto; public language: AppLanguageDto;
public languages: ImmutableArray<AppLanguageDto>; public languages: ImmutableArray<AppLanguageDto>;
@ViewChild('dueTimeSelector') @ViewChild('dueTimeSelector', { static: false })
public dueTimeSelector: DueTimeSelectorComponent; public dueTimeSelector: DueTimeSelectorComponent;
constructor(apiUrl: ApiUrlConfig, authService: AuthService, constructor(apiUrl: ApiUrlConfig, authService: AuthService,

2
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -52,7 +52,7 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
public isAllSelected = false; public isAllSelected = false;
@ViewChild('dueTimeSelector') @ViewChild('dueTimeSelector', { static: false })
public dueTimeSelector: DueTimeSelectorComponent; public dueTimeSelector: DueTimeSelectorComponent;
constructor( constructor(

4
src/Squidex/app/features/content/pages/schemas/schemas-page.component.html

@ -18,10 +18,8 @@
<ng-container content> <ng-container content>
<ng-container *ngIf="schemasState.publishedSchemas | async; let schemas"> <ng-container *ngIf="schemasState.publishedSchemas | async; let schemas">
<sqx-schema-category *ngFor="let category of schemasState.categories | async; trackBy: trackByCategory" <sqx-schema-category *ngFor="let category of schemasState.categories | async; trackBy: trackByCategory"
[name]="category" [schemaCategory]="category"
[schemas]="schemas"
[schemasFilter]="schemasFilter.valueChanges | async" [schemasFilter]="schemasFilter.valueChanges | async"
[routeSingletonToContent]="true"
[forContent]="true"> [forContent]="true">
</sqx-schema-category> </sqx-schema-category>
</ng-container> </ng-container>

10
src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts

@ -8,7 +8,11 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { AppsState, SchemasState } from '@app/shared'; import {
AppsState,
SchemaCategory,
SchemasState
} from '@app/shared';
@Component({ @Component({
selector: 'sqx-schemas-page', selector: 'sqx-schemas-page',
@ -28,8 +32,8 @@ export class SchemasPageComponent implements OnInit {
this.schemasState.load(); this.schemasState.load();
} }
public trackByCategory(index: number, category: string) { public trackByCategory(index: number, category: SchemaCategory) {
return category; return category.name;
} }
} }

4
src/Squidex/app/features/rules/pages/rules/rule-element.component.scss

@ -82,7 +82,9 @@
display: inline-block; display: inline-block;
} }
/deep/ svg { ::ng-deep {
svg {
fill: $color-dark-foreground; fill: $color-dark-foreground;
display: block; display: block;
}
} }

2
src/Squidex/app/features/schemas/pages/schema/field-wizard.component.ts

@ -29,7 +29,7 @@ const DEFAULT_FIELD = { name: '', partitioning: 'invariant', properties: createP
templateUrl: './field-wizard.component.html' templateUrl: './field-wizard.component.html'
}) })
export class FieldWizardComponent implements OnInit { export class FieldWizardComponent implements OnInit {
@ViewChild('nameInput') @ViewChild('nameInput', { static: false })
public nameInput: ElementRef<HTMLElement>; public nameInput: ElementRef<HTMLElement>;
@Input() @Input()

5
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html

@ -27,10 +27,9 @@
<ng-container content> <ng-container content>
<sqx-schema-category *ngFor="let category of schemasState.categories | async; trackBy: trackByCategory" <sqx-schema-category *ngFor="let category of schemasState.categories | async; trackBy: trackByCategory"
[name]="category" [schemaCategory]="category"
[schemas]="schemasState.schemas | async"
[schemasFilter]="schemasFilter.valueChanges | async" [schemasFilter]="schemasFilter.valueChanges | async"
(remove)="removeCategory(category)"> (remove)="removeCategory(category.name)">
</sqx-schema-category> </sqx-schema-category>
<form [formGroup]="addCategoryForm.form" (ngSubmit)="addCategory()"> <form [formGroup]="addCategoryForm.form" (ngSubmit)="addCategory()">

5
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts

@ -16,6 +16,7 @@ import {
DialogModel, DialogModel,
MessageBus, MessageBus,
ResourceOwner, ResourceOwner,
SchemaCategory,
SchemaDto, SchemaDto,
SchemasState SchemasState
} from '@app/shared'; } from '@app/shared';
@ -94,8 +95,8 @@ export class SchemasPageComponent extends ResourceOwner implements OnInit {
this.addSchemaDialog.show(); this.addSchemaDialog.show();
} }
public trackByCategory(index: number, category: string) { public trackByCategory(index: number, category: SchemaCategory) {
return category; return category.name;
} }
} }

2
src/Squidex/app/features/settings/pages/roles/role.component.ts

@ -33,7 +33,7 @@ export class RoleComponent implements OnChanges {
@Input() @Input()
public allPermissions: AutocompleteSource; public allPermissions: AutocompleteSource;
@ViewChild('addInput') @ViewChild('addInput', { static: false })
public addPermissionInput: AutocompleteComponent; public addPermissionInput: AutocompleteComponent;
public isEditing = false; public isEditing = false;

4
src/Squidex/app/framework/angular/forms/autocomplete.component.ts

@ -52,10 +52,10 @@ export class AutocompleteComponent extends StatefulControlComponent<State, any[]
@Input() @Input()
public placeholder = ''; public placeholder = '';
@ContentChild(TemplateRef) @ContentChild(TemplateRef, { static: false })
public itemTemplate: TemplateRef<any>; public itemTemplate: TemplateRef<any>;
@ViewChild('input') @ViewChild('input', { static: false })
public inputControl: ElementRef<HTMLInputElement>; public inputControl: ElementRef<HTMLInputElement>;
public queryInput = new FormControl(); public queryInput = new FormControl();

2
src/Squidex/app/framework/angular/forms/code-editor.component.ts

@ -35,7 +35,7 @@ export class CodeEditorComponent extends ExternalControlComponent<string> implem
private value: string; private value: string;
private isDisabled = false; private isDisabled = false;
@ViewChild('editor') @ViewChild('editor', { static: false })
public editor: ElementRef; public editor: ElementRef;
@Input() @Input()

2
src/Squidex/app/framework/angular/forms/date-time-editor.component.ts

@ -41,7 +41,7 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
@Input() @Input()
public hideClear: boolean; public hideClear: boolean;
@ViewChild('dateInput') @ViewChild('dateInput', { static: false })
public dateInput: ElementRef; public dateInput: ElementRef;
public timeControl = new FormControl(); public timeControl = new FormControl();

2
src/Squidex/app/framework/angular/forms/iframe-editor.component.ts

@ -26,7 +26,7 @@ export class IFrameEditorComponent extends ExternalControlComponent<any> impleme
private isDisabled = false; private isDisabled = false;
private isInitialized = false; private isInitialized = false;
@ViewChild('iframe') @ViewChild('iframe', { static: false })
public iframe: ElementRef<HTMLIFrameElement>; public iframe: ElementRef<HTMLIFrameElement>;
@Input() @Input()

2
src/Squidex/app/framework/angular/forms/json-editor.component.ts

@ -32,7 +32,7 @@ export class JsonEditorComponent extends ExternalControlComponent<string> implem
private valueString: string; private valueString: string;
private isDisabled = false; private isDisabled = false;
@ViewChild('editor') @ViewChild('editor', { static: false })
public editor: ElementRef<HTMLDivElement>; public editor: ElementRef<HTMLDivElement>;
constructor(changeDetector: ChangeDetectorRef, constructor(changeDetector: ChangeDetectorRef,

4
src/Squidex/app/framework/angular/forms/tag-editor.component.ts

@ -93,10 +93,10 @@ interface State {
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class TagEditorComponent extends StatefulControlComponent<State, any[]> implements AfterViewInit, OnInit { export class TagEditorComponent extends StatefulControlComponent<State, any[]> implements AfterViewInit, OnInit {
@ViewChild('form') @ViewChild('form', { static: false })
public formElement: ElementRef<HTMLElement>; public formElement: ElementRef<HTMLElement>;
@ViewChild('input') @ViewChild('input', { static: false })
public inputElement: ElementRef<HTMLInputElement>; public inputElement: ElementRef<HTMLInputElement>;
@Input() @Input()

4
src/Squidex/app/framework/angular/modals/modal-dialog.component.ts

@ -51,10 +51,10 @@ export class ModalDialogComponent extends StatefulComponent<State> implements Af
@Output() @Output()
public close = new EventEmitter(); public close = new EventEmitter();
@ViewChild('tabsElement') @ViewChild('tabsElement', { static: false })
public tabsElement: ElementRef<ParentNode>; public tabsElement: ElementRef<ParentNode>;
@ViewChild('footerElement') @ViewChild('footerElement', { static: false })
public footerElement: ElementRef<ParentNode>; public footerElement: ElementRef<ParentNode>;
constructor(changeDetector: ChangeDetectorRef) { constructor(changeDetector: ChangeDetectorRef) {

2
src/Squidex/app/framework/angular/modals/root-view.component.ts

@ -14,6 +14,6 @@ import { ChangeDetectionStrategy, Component, ViewChild, ViewContainerRef } from
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class RootViewComponent { export class RootViewComponent {
@ViewChild('element', { read: ViewContainerRef }) @ViewChild('element', { read: ViewContainerRef, static: false })
public viewContainer: ViewContainerRef; public viewContainer: ViewContainerRef;
} }

2
src/Squidex/app/framework/angular/panel.component.ts

@ -64,7 +64,7 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit {
@Input() @Input()
public sidebarClass = ''; public sidebarClass = '';
@ViewChild('panel') @ViewChild('panel', { static: false })
public panel: ElementRef<HTMLElement>; public panel: ElementRef<HTMLElement>;
constructor( constructor(

4
src/Squidex/app/shared/components/geolocation-editor.component.ts

@ -64,10 +64,10 @@ export class GeolocationEditorComponent extends StatefulControlComponent<State,
] ]
}); });
@ViewChild('editor') @ViewChild('editor', { static: false })
public editor: ElementRef<HTMLElement>; public editor: ElementRef<HTMLElement>;
@ViewChild('searchBox') @ViewChild('searchBox', { static: false })
public searchBoxInput: ElementRef<HTMLInputElement>; public searchBoxInput: ElementRef<HTMLInputElement>;
constructor(changeDetector: ChangeDetectorRef, constructor(changeDetector: ChangeDetectorRef,

6
src/Squidex/app/shared/components/markdown-editor.component.ts

@ -40,13 +40,13 @@ export class MarkdownEditorComponent extends StatefulControlComponent<State, str
private value: string; private value: string;
private isDisabled = false; private isDisabled = false;
@ViewChild('editor') @ViewChild('editor', { static: false })
public editor: ElementRef; public editor: ElementRef;
@ViewChild('container') @ViewChild('container', { static: false })
public container: ElementRef; public container: ElementRef;
@ViewChild('inner') @ViewChild('inner', { static: false })
public inner: ElementRef; public inner: ElementRef;
public assetsDialog = new DialogModel(); public assetsDialog = new DialogModel();

2
src/Squidex/app/shared/components/rich-editor.component.ts

@ -46,7 +46,7 @@ export class RichEditorComponent extends StatefulControlComponent<any, string> i
private value: string; private value: string;
private isDisabled = false; private isDisabled = false;
@ViewChild('editor') @ViewChild('editor', { static: false })
public editor: ElementRef; public editor: ElementRef;
@Output() @Output()

20
src/Squidex/app/shared/components/schema-category.component.html

@ -1,4 +1,4 @@
<div *ngIf="!forContent || snapshot.schemasFiltered.length > 0" dnd-droppable class="droppable category" [allowDrop]="allowDrop" (onDropSuccess)="changeCategory($event.dragData)"> <div *ngIf="!forContent || snapshot.filtered.length > 0" dnd-droppable class="droppable category" [allowDrop]="allowDrop" (onDropSuccess)="changeCategory($event.dragData)">
<div class="drop-indicator"></div> <div class="drop-indicator"></div>
<div class="header clearfix"> <div class="header clearfix">
@ -6,18 +6,18 @@
<i [class.icon-caret-right]="!snapshot.isOpen" [class.icon-caret-down]="snapshot.isOpen"></i> <i [class.icon-caret-right]="!snapshot.isOpen" [class.icon-caret-down]="snapshot.isOpen"></i>
</button> </button>
<h3>{{snapshot.displayName}} ({{snapshot.schemasFiltered.length}})</h3> <h3>{{schemaCategory.name}} ({{snapshot.filtered.length}})</h3>
<button type="button" class="btn btn-sm btn-text-secondary float-right" *ngIf="snapshot.schemasForCategory.length === 0 && !forContent" (click)="emitRemove()"> <button type="button" class="btn btn-sm btn-text-secondary float-right" *ngIf="schemaCategory.schemas.length === 0 && !forContent" (click)="emitRemove()">
<i class="icon-bin2"></i> <i class="icon-bin2"></i>
</button> </button>
</div> </div>
<ul class="nav nav-panel nav-dark nav-dark-bordered flex-column" *ngIf="snapshot.isOpen" @fade> <ul class="nav nav-panel nav-dark nav-dark-bordered flex-column" *ngIf="snapshot.isOpen" @fade>
<ng-container *ngFor="let schema of snapshot.schemasFiltered; trackBy: trackBySchema"> <ng-container *ngIf="!forContent; else simpleMode">
<li class="nav-item" dnd-draggable [dragEnabled]="!forContent && schema.canUpdateCategory" [dragData]="schema"> <li *ngFor="let schema of snapshot.filtered; trackBy: trackBySchema" class="nav-item" dnd-draggable [dragEnabled]="schema.canUpdateCategory" [dragData]="schema">
<a class="nav-link" [routerLink]="schemaRoute(schema)" routerLinkActive="active"> <a class="nav-link" [routerLink]="schemaRoute(schema)" routerLinkActive="active">
<div class="row" *ngIf="!forContent; else simpleMode"> <div class="row">
<div class="col-4"> <div class="col-4">
<span class="schema-name schema-name-accent">{{schema.displayName}}</span> <span class="schema-name schema-name-accent">{{schema.displayName}}</span>
</div> </div>
@ -32,12 +32,16 @@
<span class="item-published" [class.unpublished]="!schema.isPublished"></span> <span class="item-published" [class.unpublished]="!schema.isPublished"></span>
</div> </div>
</div> </div>
</a>
</li>
</ng-container>
<ng-template #simpleMode> <ng-template #simpleMode>
<li *ngFor="let schema of snapshot.filtered; trackBy: trackBySchema" class="nav-item">
<a class="nav-link" [routerLink]="schemaRoute(schema)" routerLinkActive="active">
<span class="schema-name" *ngIf="forContent">{{schema.displayName}}</span> <span class="schema-name" *ngIf="forContent">{{schema.displayName}}</span>
</ng-template>
</a> </a>
</li> </li>
</ng-container> </ng-template>
</ul> </ul>
</div> </div>

68
src/Squidex/app/shared/components/schema-category.component.ts

@ -10,7 +10,9 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, In
import { import {
fadeAnimation, fadeAnimation,
ImmutableArray, ImmutableArray,
isSameCategory,
LocalStoreService, LocalStoreService,
SchemaCategory,
SchemaDetailsDto, SchemaDetailsDto,
SchemaDto, SchemaDto,
SchemasState, SchemasState,
@ -19,12 +21,9 @@ import {
} from '@app/shared/internal'; } from '@app/shared/internal';
interface State { interface State {
displayName?: string; filtered: ImmutableArray<SchemaDto>;
schemasFiltered: ImmutableArray<SchemaDto>; isOpen?: boolean;
schemasForCategory: ImmutableArray<SchemaDto>;
isOpen: boolean;
} }
@Component({ @Component({
@ -38,36 +37,26 @@ interface State {
}) })
export class SchemaCategoryComponent extends StatefulComponent<State> implements OnInit, OnChanges { export class SchemaCategoryComponent extends StatefulComponent<State> implements OnInit, OnChanges {
@Input() @Input()
public name: string; public schemaCategory: SchemaCategory;
@Input()
public forContent: boolean;
@Input()
public routeSingletonToContent = false;
@Input() @Input()
public schemasFilter: string; public schemasFilter: string;
@Input() @Input()
public schemas: ImmutableArray<SchemaDto>; public forContent: boolean;
@Output() @Output()
public remove = new EventEmitter(); public remove = new EventEmitter();
public allowDrop = (schema: any) => { public allowDrop = (schema: any) => {
return (Types.is(schema, SchemaDto) || Types.is(schema, SchemaDetailsDto)) && !this.isSameCategory(schema); return (Types.is(schema, SchemaDto) || Types.is(schema, SchemaDetailsDto)) && !isSameCategory(this.schemaCategory.name, schema);
} }
constructor(changeDetector: ChangeDetectorRef, constructor(changeDetector: ChangeDetectorRef,
private readonly localStore: LocalStoreService, private readonly localStore: LocalStoreService,
private readonly schemasState: SchemasState private readonly schemasState: SchemasState
) { ) {
super(changeDetector, { super(changeDetector, { filtered: ImmutableArray.empty(), isOpen: true });
schemasFiltered: ImmutableArray.empty(),
schemasForCategory: ImmutableArray.empty(),
isOpen: true
});
} }
public ngOnInit() { public ngOnInit() {
@ -81,52 +70,37 @@ export class SchemaCategoryComponent extends StatefulComponent<State> implements
} }
public ngOnChanges(changes: SimpleChanges): void { public ngOnChanges(changes: SimpleChanges): void {
if (changes['schemas'] || changes['schemasFilter']) { if (changes['schemaCategory'] || changes['schemasFilter']) {
const isSameCategory = (schema: SchemaDto) => { let filtered = this.schemaCategory.schemas;
return (!this.name && !schema.category) || schema.category === this.name;
};
const query = this.schemasFilter; if (this.forContent) {
filtered = filtered.filter(x => x.canReadContents);
const schemasForCategory = this.schemas.filter(x => isSameCategory(x)); }
const schemasFiltered = schemasForCategory.filter(x => !query || x.name.indexOf(query) >= 0);
let isOpen = false; let isOpen = false;
if (query) { if (this.schemasFilter) {
filtered = filtered.filter(x => x.name.indexOf(this.schemasFilter) >= 0);
isOpen = true; isOpen = true;
} else { } else {
isOpen = !this.localStore.getBoolean(this.configKey()); isOpen = this.localStore.get(`schema-category.${this.schemaCategory.name}`) !== 'false';
}
this.next(s => ({ ...s, isOpen, schemasFiltered, schemasForCategory }));
}
if (changes['name']) {
let displayName = 'Schemas';
if (this.name && this.name.length > 0) {
displayName = this.name;
} }
this.next(s => ({ ...s, displayName })); this.next(s => ({ ...s, isOpen, filtered }));
} }
} }
public schemaRoute(schema: SchemaDto) { public schemaRoute(schema: SchemaDto) {
if (schema.isSingleton && this.routeSingletonToContent) { if (schema.isSingleton && this.forContent) {
return [schema.name, schema.id]; return [schema.name, schema.id];
} else { } else {
return [schema.name]; return [schema.name];
} }
} }
private isSameCategory(schema: SchemaDto): boolean {
return ((!this.name && !schema.category) || schema.category === this.name) && (!this.forContent || schema.canReadContents);
}
public changeCategory(schema: SchemaDto) { public changeCategory(schema: SchemaDto) {
this.schemasState.changeCategory(schema, this.name); this.schemasState.changeCategory(schema, this.schemaCategory.name);
} }
public emitRemove() { public emitRemove() {
@ -138,6 +112,6 @@ export class SchemaCategoryComponent extends StatefulComponent<State> implements
} }
private configKey(): string { private configKey(): string {
return `squidex.schema.category.${this.name}.closed`; return `squidex.schema.category.${this.schemaCategory.name}.closed`;
} }
} }

59
src/Squidex/app/shared/state/schemas.state.spec.ts

@ -9,11 +9,12 @@
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { IMock, It, Mock, Times } from 'typemoq'; import { IMock, It, Mock, Times } from 'typemoq';
import { SchemasState } from './schemas.state'; import { SchemaCategory, SchemasState } from './schemas.state';
import { import {
DialogService, DialogService,
FieldDto, FieldDto,
ImmutableArray,
SchemaDetailsDto, SchemaDetailsDto,
SchemasService, SchemasService,
UpdateSchemaCategoryDto, UpdateSchemaCategoryDto,
@ -70,7 +71,13 @@ describe('SchemasState', () => {
expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas.items); expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas.items);
expect(schemasState.snapshot.isLoaded).toBeTruthy(); expect(schemasState.snapshot.isLoaded).toBeTruthy();
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false });
const categories = getCategories(schemasState);
expect(categories!).toEqual([
{ name: 'category1', upper: 'CATEGORY1', schemas: ImmutableArray.of([schema1]) },
{ name: 'category2', upper: 'CATEGORY2', schemas: ImmutableArray.of([schema2]) }
]);
schemasService.verifyAll(); schemasService.verifyAll();
}); });
@ -84,7 +91,14 @@ describe('SchemasState', () => {
expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas.items); expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas.items);
expect(schemasState.snapshot.isLoaded).toBeTruthy(); expect(schemasState.snapshot.isLoaded).toBeTruthy();
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true });
const categories = getCategories(schemasState);
expect(categories!).toEqual([
{ name: 'category1', upper: 'CATEGORY1', schemas: ImmutableArray.of([schema1]) },
{ name: 'category2', upper: 'CATEGORY2', schemas: ImmutableArray.of([schema2]) },
{ name: 'category3', upper: 'CATEGORY3', schemas: ImmutableArray.empty() }
]);
schemasService.verifyAll(); schemasService.verifyAll();
}); });
@ -112,13 +126,36 @@ describe('SchemasState', () => {
it('should add category', () => { it('should add category', () => {
schemasState.addCategory('category3'); schemasState.addCategory('category3');
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true }); const categories = getCategories(schemasState);
expect(categories!).toEqual([
{ name: 'category1', upper: 'CATEGORY1', schemas: ImmutableArray.of([schema1]) },
{ name: 'category2', upper: 'CATEGORY2', schemas: ImmutableArray.of([schema2]) },
{ name: 'category3', upper: 'CATEGORY3', schemas: ImmutableArray.empty() }
]);
});
it('should not remove category with schemas', () => {
schemasState.addCategory('category1');
const categories = getCategories(schemasState);
expect(categories!).toEqual([
{ name: 'category1', upper: 'CATEGORY1', schemas: ImmutableArray.of([schema1]) },
{ name: 'category2', upper: 'CATEGORY2', schemas: ImmutableArray.of([schema2]) }
]);
}); });
it('should remove category', () => { it('should remove category', () => {
schemasState.removeCategory('category1'); schemasState.addCategory('category3');
schemasState.removeCategory('category3');
expect(schemasState.snapshot.categories).toEqual({ 'category2': false }); const categories = getCategories(schemasState);
expect(categories!).toEqual([
{ name: 'category1', upper: 'CATEGORY1', schemas: ImmutableArray.of([schema1]) },
{ name: 'category2', upper: 'CATEGORY2', schemas: ImmutableArray.of([schema2]) }
]);
}); });
it('should return schema on select and reload when already loaded', () => { it('should return schema on select and reload when already loaded', () => {
@ -494,3 +531,13 @@ describe('SchemasState', () => {
}); });
}); });
}); });
function getCategories(schemasState: SchemasState) {
let categories: SchemaCategory[];
schemasState.categories.subscribe(result => {
categories = result;
});
return categories!;
}

71
src/Squidex/app/shared/state/schemas.state.ts

@ -38,7 +38,7 @@ type AnyFieldDto = NestedFieldDto | RootFieldDto;
interface Snapshot { interface Snapshot {
// The schema categories. // The schema categories.
categories: { [name: string]: boolean }; categories: string[];
// The current schemas. // The current schemas.
schemas: SchemasList; schemas: SchemasList;
@ -54,8 +54,7 @@ interface Snapshot {
} }
export type SchemasList = ImmutableArray<SchemaDto>; export type SchemasList = ImmutableArray<SchemaDto>;
export type SchemaCategory = { name: string; schemas: SchemasList; upper: string; };
export type Categories = { [name: string]: boolean };
function sameSchema(lhs: SchemaDetailsDto | null, rhs?: SchemaDetailsDto | null): boolean { function sameSchema(lhs: SchemaDetailsDto | null, rhs?: SchemaDetailsDto | null): boolean {
return lhs === rhs || (!!lhs && !!rhs && lhs.id === rhs.id && lhs.version === rhs.version); return lhs === rhs || (!!lhs && !!rhs && lhs.id === rhs.id && lhs.version === rhs.version);
@ -68,7 +67,7 @@ export class SchemasState extends State<Snapshot> {
} }
public categories = public categories =
this.project2(x => x.categories, x => sortedCategoryNames(x)); this.project2(x => x, x => buildCategories(x.categories, x.schemas));
public selectedSchema = public selectedSchema =
this.project(x => x.selectedSchema, sameSchema); this.project(x => x.selectedSchema, sameSchema);
@ -90,7 +89,7 @@ export class SchemasState extends State<Snapshot> {
private readonly dialogs: DialogService, private readonly dialogs: DialogService,
private readonly schemasService: SchemasService private readonly schemasService: SchemasService
) { ) {
super({ schemas: ImmutableArray.empty(), categories: buildCategories({}) }); super({ schemas: ImmutableArray.empty(), categories: [] });
} }
public select(idOrName: string | null): Observable<SchemaDetailsDto | null> { public select(idOrName: string | null): Observable<SchemaDetailsDto | null> {
@ -126,9 +125,7 @@ export class SchemasState extends State<Snapshot> {
return this.next(s => { return this.next(s => {
const schemas = ImmutableArray.of(items).sortByStringAsc(x => x.displayName); const schemas = ImmutableArray.of(items).sortByStringAsc(x => x.displayName);
const categories = buildCategories(s.categories, schemas); return { ...s, schemas, isLoaded: true, canCreate };
return { ...s, schemas, isLoaded: true, categories, canCreate };
}); });
}), }),
shareSubscribed(this.dialogs)); shareSubscribed(this.dialogs));
@ -140,9 +137,7 @@ export class SchemasState extends State<Snapshot> {
this.next(s => { this.next(s => {
const schemas = s.schemas.push(created).sortByStringAsc(x => x.displayName); const schemas = s.schemas.push(created).sortByStringAsc(x => x.displayName);
const categories = buildCategories(s.categories, schemas); return { ...s, schemas };
return { ...s, schemas, categories };
}); });
}), }),
shareSubscribed(this.dialogs, { silent: true })); shareSubscribed(this.dialogs, { silent: true }));
@ -163,7 +158,7 @@ export class SchemasState extends State<Snapshot> {
public addCategory(name: string) { public addCategory(name: string) {
this.next(s => { this.next(s => {
const categories = addCategory(s.categories, name); const categories = [...s.categories, name];
return { ...s, categories: categories }; return { ...s, categories: categories };
}); });
@ -171,7 +166,7 @@ export class SchemasState extends State<Snapshot> {
public removeCategory(name: string) { public removeCategory(name: string) {
this.next(s => { this.next(s => {
const categories = removeCategory(s.categories, name); const categories = s.categories.filter(x => x !== name);
return { ...s, categories: categories }; return { ...s, categories: categories };
}); });
@ -309,9 +304,7 @@ export class SchemasState extends State<Snapshot> {
schema : schema :
s.selectedSchema; s.selectedSchema;
const categories = buildCategories(s.categories, schemas); return { ...s, schemas, selectedSchema };
return { ...s, schemas, selectedSchema, categories };
}); });
} }
@ -328,46 +321,32 @@ function getField(x: SchemaDetailsDto, request: AddFieldDto, parent?: RootFieldD
} }
} }
function buildCategories(categories: { [name: string]: boolean }, schemas?: SchemasList) { function buildCategories(categories: string[], schemas: SchemasList): SchemaCategory[] {
categories = { ...categories }; const uniqueCategories: { [name: string]: string } = {};
for (let category in categories) { for (let category of categories) {
if (categories.hasOwnProperty(category)) { uniqueCategories[category] = category;
if (!categories[category]) {
delete categories[category];
}
}
} }
if (schemas) {
for (let schema of schemas.values) { for (let schema of schemas.values) {
categories[schema.category || ''] = false; uniqueCategories[schema.category || 'Schemas'] = schema.category;
}
} }
return categories; const result: SchemaCategory[] = [];
}
function addCategory(categories: Categories, category: string) {
categories = { ...categories };
categories[category] = true; for (let name in uniqueCategories) {
if (uniqueCategories.hasOwnProperty(name)) {
return categories; const key = uniqueCategories[name];
}
function removeCategory(categories: Categories, category: string) { result.push({ name, upper: name.toUpperCase(), schemas: schemas.filter(x => isSameCategory(key, x))});
categories = { ...categories }; }
}
delete categories[category]; result.sort((a, b) => compareStringsAsc(a.upper, b.upper));
return categories; return result;
} }
function sortedCategoryNames(categories: Categories) { export function isSameCategory(name: string, schema: SchemaDto): boolean {
const names = Object.keys(categories); return (!name && !schema.category) || schema.category === name;
names.sort(compareStringsAsc);
return names;
} }

2
src/Squidex/app/shell/pages/internal/internal-area.component.scss

@ -2,7 +2,7 @@
@import '_mixins'; @import '_mixins';
.navbar { .navbar {
@include box-shadow(0, 3px, 5px, .13px); @include box-shadow(0, 3px, 5px, .13);
display: block; display: block;
} }

103
src/Squidex/app/shims.ts

@ -5,20 +5,91 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import 'core-js/es6/array'; // ES2015 symbol capabilities
import 'core-js/es6/date'; import 'core-js/modules/es.symbol';
import 'core-js/es6/function';
import 'core-js/es6/map'; // ES2015 function capabilities
import 'core-js/es6/math'; import 'core-js/modules/es.function.bind';
import 'core-js/es6/number'; import 'core-js/modules/es.function.has-instance';
import 'core-js/es6/object'; import 'core-js/modules/es.function.name';
import 'core-js/es6/parse-float';
import 'core-js/es6/parse-int'; // ES2015 object capabilities
import 'core-js/es6/reflect'; import 'core-js/modules/es.object.assign';
import 'core-js/es6/regexp'; import 'core-js/modules/es.object.create';
import 'core-js/es6/set'; import 'core-js/modules/es.object.define-properties';
import 'core-js/es6/string'; import 'core-js/modules/es.object.define-property';
import 'core-js/es6/symbol'; import 'core-js/modules/es.object.freeze';
import 'core-js/modules/es.object.get-own-property-descriptor';
import 'core-js/es7/reflect'; import 'core-js/modules/es.object.get-own-property-names';
import 'core-js/modules/es.object.get-prototype-of';
import 'core-js/modules/es.object.is';
import 'core-js/modules/es.object.is-extensible';
import 'core-js/modules/es.object.is-frozen';
import 'core-js/modules/es.object.is-sealed';
import 'core-js/modules/es.object.keys';
import 'core-js/modules/es.object.prevent-extensions';
import 'core-js/modules/es.object.seal';
import 'core-js/modules/es.object.set-prototype-of';
import 'core-js/modules/es.object.to-string';
// ES2015 array capabilities
import 'core-js/modules/es.array.copy-within';
import 'core-js/modules/es.array.every';
import 'core-js/modules/es.array.fill';
import 'core-js/modules/es.array.filter';
import 'core-js/modules/es.array.find';
import 'core-js/modules/es.array.find-index';
import 'core-js/modules/es.array.for-each';
import 'core-js/modules/es.array.from';
import 'core-js/modules/es.array.index-of';
import 'core-js/modules/es.array.is-array';
import 'core-js/modules/es.array.iterator';
import 'core-js/modules/es.array.join';
import 'core-js/modules/es.array.last-index-of';
import 'core-js/modules/es.array.map';
import 'core-js/modules/es.array.of';
import 'core-js/modules/es.array.reduce';
import 'core-js/modules/es.array.reduce-right';
import 'core-js/modules/es.array.slice';
import 'core-js/modules/es.array.some';
import 'core-js/modules/es.array.sort';
// ES2015 string capabilities
import 'core-js/modules/es.string.anchor';
import 'core-js/modules/es.string.big';
import 'core-js/modules/es.string.blink';
import 'core-js/modules/es.string.bold';
import 'core-js/modules/es.string.code-point-at';
import 'core-js/modules/es.string.ends-with';
import 'core-js/modules/es.string.fixed';
import 'core-js/modules/es.string.fontcolor';
import 'core-js/modules/es.string.fontsize';
import 'core-js/modules/es.string.from-code-point';
import 'core-js/modules/es.string.includes';
import 'core-js/modules/es.string.italics';
import 'core-js/modules/es.string.iterator';
import 'core-js/modules/es.string.link';
import 'core-js/modules/es.string.raw';
import 'core-js/modules/es.string.repeat';
import 'core-js/modules/es.string.small';
import 'core-js/modules/es.string.starts-with';
import 'core-js/modules/es.string.strike';
import 'core-js/modules/es.string.sub';
import 'core-js/modules/es.string.sup';
import 'core-js/modules/es.string.trim';
import 'core-js/modules/es.parse-float';
import 'core-js/modules/es.parse-int';
import 'core-js/es/date';
import 'core-js/es/math';
import 'core-js/es/number';
import 'core-js/es/regexp';
import 'core-js/modules/es.map';
import 'core-js/modules/es.promise';
import 'core-js/modules/es.set';
import 'core-js/modules/es.weak-map';
import 'core-js/modules/web.dom-collections.iterator';
import 'zone.js/dist/zone'; import 'zone.js/dist/zone';

2
src/Squidex/app/theme/_bootstrap.scss

@ -162,7 +162,7 @@ a {
.dropdown-menu { .dropdown-menu {
// White dropdown menu without border and shadow. // White dropdown menu without border and shadow.
& { & {
@include box-shadow(0, 3px, 16px, .2px); @include box-shadow(0, 3px, 16px, .2);
border: 0; border: 0;
background: $panel-light-background; background: $panel-light-background;
} }

2767
src/Squidex/package-lock.json

File diff suppressed because it is too large

81
src/Squidex/package.json

@ -6,69 +6,67 @@
"repository": "https://github.com/SebastianStehle/Squidex", "repository": "https://github.com/SebastianStehle/Squidex",
"scripts": { "scripts": {
"copy": "cpx node_modules/oidc-client/dist/oidc-client.min.js wwwroot/scripts/", "copy": "cpx node_modules/oidc-client/dist/oidc-client.min.js wwwroot/scripts/",
"start": "npm run copy && webpack-dev-server --config app-config/webpack.run.dev.js --inline --port 3000 --hot", "start": "npm run copy && webpack-dev-server --config app-config/webpack.config.js --inline --port 3000 --hot",
"test": "karma start", "test": "karma start",
"test:coverage": "karma start karma.coverage.conf.js", "test:coverage": "karma start karma.coverage.conf.js",
"test:clean": "rimraf _test-output", "test:clean": "rimraf _test-output",
"tslint": "tslint -c tslint.json -p tsconfig.json app/**/*.ts", "tslint": "tslint -c tslint.json -p tsconfig.json app/**/*.ts",
"build": "npm run copy && webpack --config app-config/webpack.run.prod.js", "build": "npm run copy && node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js --config app-config/webpack.config.js --env.production",
"build:clean": "rimraf wwwroot/build" "build:clean": "rimraf wwwroot/build"
}, },
"dependencies": { "dependencies": {
"@angular/animations": "7.2.14", "@angular/animations": "8.0.2",
"@angular/common": "7.2.14", "@angular/common": "8.0.2",
"@angular/core": "7.2.14", "@angular/core": "8.0.2",
"@angular/forms": "7.2.14", "@angular/forms": "8.0.2",
"@angular/http": "7.2.14", "@angular/http": "7.2.15",
"@angular/platform-browser-dynamic": "7.2.14", "@angular/platform-browser": "8.0.2",
"@angular/platform-browser": "7.2.14", "@angular/platform-browser-dynamic": "8.0.2",
"@angular/platform-server": "7.2.14", "@angular/platform-server": "8.0.2",
"@angular/router": "7.2.14", "@angular/router": "8.0.2",
"angular2-chartjs": "0.5.1", "angular2-chartjs": "0.5.1",
"babel-polyfill": "6.26.0", "babel-polyfill": "6.26.0",
"bootstrap": "4.3.1", "bootstrap": "4.3.1",
"core-js": "2.6.3", "core-js": "3.1.4",
"graphiql": "0.13.0", "graphiql": "0.13.2",
"graphql": "14.2.1", "graphql": "14.3.1",
"marked": "0.6.2", "marked": "0.6.2",
"moment": "2.24.0", "moment": "2.24.0",
"mousetrap": "1.6.3", "mousetrap": "1.6.3",
"ng2-dnd": "5.0.2", "ng2-dnd": "5.0.2",
"ngx-color-picker": "7.5.0", "ngx-color-picker": "8.0.1",
"oidc-client": "1.7.1", "oidc-client": "1.8.2",
"pikaday": "1.8.0", "pikaday": "1.8.0",
"progressbar.js": "1.0.1", "progressbar.js": "1.0.1",
"react-dom": "16.8.6",
"react": "16.8.6", "react": "16.8.6",
"rxjs": "6.5.1", "react-dom": "16.8.6",
"rxjs": "6.5.2",
"slugify": "1.3.4", "slugify": "1.3.4",
"sortablejs": "1.9.0", "sortablejs": "1.9.0",
"tslib": "1.9.3", "tslib": "1.10.0",
"zone.js": "0.9.1" "zone.js": "0.9.1"
}, },
"devDependencies": { "devDependencies": {
"@angular/compiler": "7.2.14", "@angular/compiler": "8.0.2",
"@angular/compiler-cli": "7.2.14", "@angular/compiler-cli": "8.0.2",
"@ngtools/webpack": "7.3.8", "@ngtools/webpack": "8.0.3",
"@types/core-js": "2.5.0", "@types/core-js": "2.5.2",
"@types/jasmine": "3.3.12", "@types/jasmine": "3.3.13",
"@types/marked": "0.6.5", "@types/marked": "0.6.5",
"@types/mousetrap": "1.6", "@types/mousetrap": "1.6",
"@types/node": "12.0.0", "@types/node": "12.0.10",
"@types/react": "16.8.16", "@types/react": "16.8.22",
"@types/react-dom": "16.8.4", "@types/react-dom": "16.8.4",
"@types/sortablejs": "1.7.2", "@types/sortablejs": "1.7.2",
"angular-router-loader": "0.8.5",
"angular2-template-loader": "0.6.2",
"awesome-typescript-loader": "5.2.1", "awesome-typescript-loader": "5.2.1",
"babel-core": "6.26.3", "babel-core": "6.26.3",
"browserslist": "^4.6.3", "browserslist": "^4.6.3",
"caniuse-lite": "^1.0.30000975", "caniuse-lite": "^1.0.30000976",
"circular-dependency-plugin": "5.0.2", "circular-dependency-plugin": "5.0.2",
"codelyzer": "5.0.1", "codelyzer": "5.1.0",
"cpx": "1.5.0", "cpx": "1.5.0",
"css-loader": "2.1.1", "css-loader": "3.0.0",
"file-loader": "3.0.1", "file-loader": "4.0.0",
"html-loader": "0.5.5", "html-loader": "0.5.5",
"html-webpack-plugin": "3.2.0", "html-webpack-plugin": "3.2.0",
"ignore-loader": "0.1.2", "ignore-loader": "0.1.2",
@ -83,8 +81,8 @@
"karma-jasmine-html-reporter": "1.4.2", "karma-jasmine-html-reporter": "1.4.2",
"karma-mocha-reporter": "2.2.5", "karma-mocha-reporter": "2.2.5",
"karma-sourcemap-loader": "0.3.7", "karma-sourcemap-loader": "0.3.7",
"karma-webpack": "3.0.5", "karma-webpack": "4.0.2",
"mini-css-extract-plugin": "0.6.0", "mini-css-extract-plugin": "0.7.0",
"node-sass": "4.12.0", "node-sass": "4.12.0",
"optimize-css-assets-webpack-plugin": "5.0.1", "optimize-css-assets-webpack-plugin": "5.0.1",
"raw-loader": "1.0.0", "raw-loader": "1.0.0",
@ -93,17 +91,16 @@
"sass-lint": "1.13.1", "sass-lint": "1.13.1",
"sass-loader": "7.1.0", "sass-loader": "7.1.0",
"style-loader": "0.23.1", "style-loader": "0.23.1",
"ts-loader": "5.4.5", "ts-loader": "6.0.4",
"tsconfig-paths-webpack-plugin": "3.2.0", "tsconfig-paths-webpack-plugin": "3.2.0",
"tslint": "5.16.0", "tslint": "5.18.0",
"tslint-webpack-plugin": "2.0.4", "tslint-webpack-plugin": "2.0.4",
"typemoq": "2.1.0", "typemoq": "2.1.0",
"typescript": "3.2.4", "typescript": "3.4.3",
"uglifyjs-webpack-plugin": "2.1.2", "uglifyjs-webpack-plugin": "2.1.3",
"underscore": "1.9.1", "underscore": "1.9.1",
"webpack": "4.30.0", "webpack": "4.35.0",
"webpack-cli": "3.3.1", "webpack-cli": "3.3.4",
"webpack-dev-server": "3.3.1", "webpack-dev-server": "3.7.2"
"webpack-merge": "4.2.1"
} }
} }

1
src/Squidex/tsconfig.json

@ -3,6 +3,7 @@
"baseUrl": ".", "baseUrl": ".",
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"incremental": true,
"importHelpers": true, "importHelpers": true,
"lib": ["es6", "esnext", "dom"], "lib": ["es6", "esnext", "dom"],
"moduleResolution": "node", "moduleResolution": "node",

Loading…
Cancel
Save