Browse Source

refactoring

pull/22829/head
erdemcaygor 9 months ago
parent
commit
2c5c8ad8de
  1. 4
      npm/ng-packs/packages/account/src/lib/account.module.ts
  2. 10
      npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts
  3. 2
      npm/ng-packs/packages/account/src/lib/components/login/login.component.ts
  4. 6
      npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings-half-row.component.ts
  5. 8
      npm/ng-packs/packages/account/src/lib/components/register/register.component.ts
  6. 2
      npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html
  7. 37
      npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.ts
  8. 4
      npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts
  9. 4
      npm/ng-packs/packages/identity/src/lib/identity.module.ts
  10. 4
      npm/ng-packs/packages/setting-management/src/lib/components/setting-management.component.ts
  11. 4
      npm/ng-packs/packages/setting-management/src/lib/setting-management.module.ts
  12. 4
      npm/ng-packs/packages/tenant-management/src/lib/tenant-management.module.ts
  13. 6
      npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/account-layout.component.ts
  14. 4
      npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/auth-wrapper/auth-wrapper.component.ts
  15. 13
      npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/tenant-box/tenant-box.component.ts
  16. 6
      npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.ts
  17. 13
      npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts
  18. 4
      npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts
  19. 4
      npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts
  20. 4
      npm/ng-packs/packages/theme-basic/src/lib/components/page-alert-container/page-alert-container.component.ts
  21. 20
      npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts
  22. 6
      npm/ng-packs/packages/theme-basic/src/lib/components/validation-error/validation-error.component.ts
  23. 3
      npm/ng-packs/packages/theme-shared/src/lib/components/password/password.component.ts
  24. 8
      templates/app/angular/src/app/app.routes.ts
  25. 8
      templates/module/angular/projects/dev-app/src/app/app.routes.ts

4
npm/ng-packs/packages/account/src/lib/account.module.ts

@ -51,7 +51,9 @@ export class AccountModule {
],
};
}
/**
* @deprecated `AccountModule.forLazy()` is deprecated. You can use `createRoutes` **function** instead.
*/
static forLazy(options = {} as AccountConfigOptions): NgModuleFactory<AccountModule> {
return new LazyModuleFactory(AccountModule.forChild(options));
}

10
npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts

@ -12,7 +12,7 @@ import { finalize } from 'rxjs/operators';
import { Account } from '../../models';
import { ManageProfileStateService } from '../../services';
import { comparePasswords, NgxValidateCoreModule, Validation } from '@ngx-validate/core';
import { LocalizationPipe } from '@abp/ng.core';
import { AutofocusDirective, LocalizationPipe } from '@abp/ng.core';
const { required } = Validators;
const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword'];
@ -21,7 +21,13 @@ const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword'];
selector: 'abp-change-password-form',
templateUrl: './change-password.component.html',
exportAs: 'abpChangePasswordForm',
imports: [ReactiveFormsModule, LocalizationPipe, ButtonComponent, NgxValidateCoreModule],
imports: [
ReactiveFormsModule,
LocalizationPipe,
ButtonComponent,
NgxValidateCoreModule,
AutofocusDirective,
],
})
export class ChangePasswordComponent
implements OnInit, Account.ChangePasswordComponentInputs, Account.ChangePasswordComponentOutputs

2
npm/ng-packs/packages/account/src/lib/components/login/login.component.ts

@ -10,6 +10,7 @@ import { throwError } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import {
AuthService,
AutofocusDirective,
ConfigStateService,
LocalizationPipe,
NgxValidateCoreModule,
@ -29,6 +30,7 @@ const { maxLength, required } = Validators;
LocalizationPipe,
ButtonComponent,
NgxValidateCoreModule,
AutofocusDirective,
],
})
export class LoginComponent implements OnInit {

6
npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings-half-row.component.ts

@ -5,9 +5,7 @@ import {
EXTENSIBLE_FORM_VIEW_PROVIDER,
} from '@abp/ng.components/extensible';
import { ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { CoreModule } from '@abp/ng.core';
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { LocalizationPipe } from '@abp/ng.core';
@Component({
selector: 'abp-personal-settings-half-row',
@ -23,7 +21,7 @@ import { ThemeSharedModule } from '@abp/ng.theme.shared';
</div>`,
styles: [],
viewProviders: [EXTENSIBLE_FORM_VIEW_PROVIDER],
imports: [CommonModule, ReactiveFormsModule, CoreModule, ThemeSharedModule],
imports: [ReactiveFormsModule, LocalizationPipe],
})
export class PersonalSettingsHalfRowComponent {
public displayName: string;

8
npm/ng-packs/packages/account/src/lib/components/register/register.component.ts

@ -1,5 +1,10 @@
import { AccountService, RegisterDto } from '@abp/ng.account.core/proxy';
import { AuthService, ConfigStateService, LocalizationPipe } from '@abp/ng.core';
import {
AuthService,
AutofocusDirective,
ConfigStateService,
LocalizationPipe,
} from '@abp/ng.core';
import { ButtonComponent, getPasswordValidators, ToasterService } from '@abp/ng.theme.shared';
import { Component, Injector, OnInit } from '@angular/core';
import {
@ -26,6 +31,7 @@ const { maxLength, required, email } = Validators;
NgxValidateCoreModule,
LocalizationPipe,
ButtonComponent,
AutofocusDirective,
],
})
export class RegisterComponent implements OnInit {

2
npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html

@ -15,7 +15,7 @@
</ng-template>
<ng-template #abpBody>
<form [formGroup]="form" (ngSubmit)="save()" validateOnSubmit>
<form [formGroup]="form" (ngSubmit)="save()" [validateOnSubmit]="true">
<abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
</form>
</ng-template>

37
npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.ts

@ -1,32 +1,37 @@
import {
CoreModule,
InitDirective,
ListService,
LocalizationPipe,
PagedAndSortedResultRequestDto,
PagedResultDto,
ReplaceableTemplateDirective,
} from '@abp/ng.core';
import { IdentityRoleDto, IdentityRoleService } from '@abp/ng.identity/proxy';
import {
ePermissionManagementComponents,
PermissionManagementModule,
PermissionManagementComponent,
} from '@abp/ng.permission-management';
import {
ButtonComponent,
Confirmation,
ConfirmationService,
ThemeSharedModule,
ModalCloseDirective,
ModalComponent,
ToasterService,
} from '@abp/ng.theme.shared';
import {
ExtensibleModule,
ExtensibleFormComponent,
ExtensibleTableComponent,
EXTENSIONS_IDENTIFIER,
FormPropData,
generateFormFromProps,
} from '@abp/ng.components/extensible';
import { Component, inject, Injector, OnInit } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
import { finalize } from 'rxjs/operators';
import { eIdentityComponents } from '../../enums/components';
import { CommonModule } from '@angular/common';
import { PageModule } from '@abp/ng.components/page';
import { PageComponent } from '@abp/ng.components/page';
import { NgxValidateCoreModule } from '@ngx-validate/core';
@Component({
selector: 'abp-roles',
@ -39,12 +44,18 @@ import { PageModule } from '@abp/ng.components/page';
},
],
imports: [
CommonModule,
CoreModule,
ThemeSharedModule,
ExtensibleModule,
PageModule,
PermissionManagementModule,
ReactiveFormsModule,
LocalizationPipe,
ExtensibleTableComponent,
ModalComponent,
ButtonComponent,
PageComponent,
ExtensibleFormComponent,
ModalCloseDirective,
PermissionManagementComponent,
ReplaceableTemplateDirective,
NgxValidateCoreModule,
InitDirective,
],
})
export class RolesComponent implements OnInit {

4
npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts

@ -13,7 +13,7 @@ import {
} from '@abp/ng.identity/proxy';
import {
ePermissionManagementComponents,
PermissionManagementModule,
PermissionManagementComponent,
} from '@abp/ng.permission-management';
import {
ButtonComponent,
@ -68,7 +68,7 @@ import { NgxValidateCoreModule } from '@ngx-validate/core';
imports: [
ReactiveFormsModule,
FormsModule,
PermissionManagementModule,
PermissionManagementComponent,
PageComponent,
NgbNavModule,
NgbDropdownModule,

4
npm/ng-packs/packages/identity/src/lib/identity.module.ts

@ -46,7 +46,9 @@ export class IdentityModule {
],
};
}
/**
* @deprecated `IdentityModule.forLazy()` is deprecated. You can use `createRoutes` **function** instead.
*/
static forLazy(options: IdentityConfigOptions = {}): NgModuleFactory<IdentityModule> {
return new LazyModuleFactory(IdentityModule.forChild(options));
}

4
npm/ng-packs/packages/setting-management/src/lib/components/setting-management.component.ts

@ -3,12 +3,12 @@ import { SettingTabsService } from '@abp/ng.setting-management/config';
import { Component, OnDestroy, OnInit, TrackByFunction } from '@angular/core';
import { Subscription } from 'rxjs';
import { CommonModule } from '@angular/common';
import { PageModule } from '@abp/ng.components/page';
import { PageComponent } from '@abp/ng.components/page';
@Component({
selector: 'abp-setting-management',
templateUrl: './setting-management.component.html',
imports: [CommonModule, PageModule, LocalizationPipe, PermissionDirective, ForDirective],
imports: [CommonModule, PageComponent, LocalizationPipe, PermissionDirective, ForDirective],
})
export class SettingManagementComponent implements OnDestroy, OnInit {
private subscription = new Subscription();

4
npm/ng-packs/packages/setting-management/src/lib/setting-management.module.ts

@ -17,7 +17,9 @@ export class SettingManagementModule {
providers: [],
};
}
/**
* @deprecated `SettingManagementModule.forLazy()` is deprecated. You can use `createRoutes` **function** instead.
*/
static forLazy(): NgModuleFactory<SettingManagementModule> {
return new LazyModuleFactory(SettingManagementModule.forChild());
}

4
npm/ng-packs/packages/tenant-management/src/lib/tenant-management.module.ts

@ -48,7 +48,9 @@ export class TenantManagementModule {
],
};
}
/**
* @deprecated `TenantManagementModule.forLazy()` is deprecated. You can use `createRoutes` **function** instead.
*/
static forLazy(
options: TenantManagementConfigOptions = {},
): NgModuleFactory<TenantManagementModule> {

6
npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/account-layout.component.ts

@ -1,5 +1,5 @@
import { AfterViewInit, Component } from '@angular/core';
import { CoreModule, eLayoutType, SubscriptionService } from '@abp/ng.core';
import { eLayoutType, ReplaceableTemplateDirective, SubscriptionService } from '@abp/ng.core';
import { LayoutService } from '../../services/layout.service';
import { CommonModule } from '@angular/common';
import { LogoComponent } from '../logo/logo.component';
@ -7,6 +7,7 @@ import { RoutesComponent } from '../routes/routes.component';
import { NavItemsComponent } from '../nav-items/nav-items.component';
import { AuthWrapperComponent } from './auth-wrapper/auth-wrapper.component';
import { PageAlertContainerComponent } from '../page-alert-container/page-alert-container.component';
import { RouterModule } from '@angular/router';
@Component({
selector: 'abp-layout-account',
@ -14,12 +15,13 @@ import { PageAlertContainerComponent } from '../page-alert-container/page-alert-
providers: [LayoutService, SubscriptionService],
imports: [
CommonModule,
CoreModule,
LogoComponent,
RoutesComponent,
NavItemsComponent,
AuthWrapperComponent,
PageAlertContainerComponent,
ReplaceableTemplateDirective,
RouterModule,
],
})
export class AccountLayoutComponent implements AfterViewInit {

4
npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/auth-wrapper/auth-wrapper.component.ts

@ -1,14 +1,14 @@
import { AuthWrapperService } from '@abp/ng.account.core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CoreModule } from '@abp/ng.core';
import { LocalizationPipe, ReplaceableTemplateDirective } from '@abp/ng.core';
import { TenantBoxComponent } from '../tenant-box/tenant-box.component';
@Component({
selector: 'abp-auth-wrapper',
templateUrl: './auth-wrapper.component.html',
providers: [AuthWrapperService],
imports: [CommonModule, CoreModule, TenantBoxComponent],
imports: [CommonModule, TenantBoxComponent, ReplaceableTemplateDirective, LocalizationPipe],
})
export class AuthWrapperComponent {
constructor(public service: AuthWrapperService) {}

13
npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/tenant-box/tenant-box.component.ts

@ -1,13 +1,22 @@
import { TenantBoxService } from '@abp/ng.account.core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CoreModule } from '@abp/ng.core';
import { LocalizationPipe } from '@abp/ng.core';
import { ButtonComponent, ModalCloseDirective, ModalComponent } from '@abp/ng.theme.shared';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'abp-tenant-box',
templateUrl: './tenant-box.component.html',
providers: [TenantBoxService],
imports: [CommonModule, CoreModule],
imports: [
CommonModule,
FormsModule,
ModalComponent,
LocalizationPipe,
ButtonComponent,
ModalCloseDirective,
],
})
export class TenantBoxComponent {
constructor(public service: TenantBoxService) {}

6
npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.ts

@ -1,8 +1,9 @@
import { CoreModule, eLayoutType, SubscriptionService } from '@abp/ng.core';
import { eLayoutType, ReplaceableTemplateDirective, SubscriptionService } from '@abp/ng.core';
import { collapseWithMargin, slideFromBottom } from '@abp/ng.theme.shared';
import { AfterViewInit, Component, inject } from '@angular/core';
import { LayoutService } from '../../services/layout.service';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { LogoComponent } from '../logo/logo.component';
import { PageAlertContainerComponent } from '../page-alert-container/page-alert-container.component';
import { RoutesComponent } from '../routes/routes.component';
@ -15,11 +16,12 @@ import { NavItemsComponent } from '../nav-items/nav-items.component';
providers: [LayoutService, SubscriptionService],
imports: [
CommonModule,
CoreModule,
LogoComponent,
PageAlertContainerComponent,
RoutesComponent,
NavItemsComponent,
ReplaceableTemplateDirective,
RouterModule,
],
})
export class ApplicationLayoutComponent implements AfterViewInit {

13
npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts

@ -1,10 +1,12 @@
import {
AuthService,
ConfigStateService,
CoreModule,
CurrentUserDto,
LocalizationPipe,
NAVIGATE_TO_MANAGE_PROFILE,
PermissionDirective,
SessionStateService,
ToInjectorPipe,
} from '@abp/ng.core';
import { AbpVisibleDirective, UserMenu, UserMenuService } from '@abp/ng.theme.shared';
import { Component, Inject, TrackByFunction } from '@angular/core';
@ -15,7 +17,14 @@ import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'abp-current-user',
templateUrl: './current-user.component.html',
imports: [CommonModule, CoreModule, NgbDropdownModule, AbpVisibleDirective],
imports: [
CommonModule,
NgbDropdownModule,
AbpVisibleDirective,
PermissionDirective,
ToInjectorPipe,
LocalizationPipe,
],
})
export class CurrentUserComponent {
currentUser$: Observable<CurrentUserDto> = this.configState.getOne$('currentUser');

4
npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts

@ -1,4 +1,4 @@
import { ConfigStateService, CoreModule, LanguageInfo, SessionStateService } from '@abp/ng.core';
import { ConfigStateService, LanguageInfo, SessionStateService } from '@abp/ng.core';
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@ -39,7 +39,7 @@ import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
</div>
}
`,
imports: [CommonModule, CoreModule, NgbDropdownModule],
imports: [CommonModule, NgbDropdownModule],
})
export class LanguagesComponent {
get smallScreen(): boolean {

4
npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts

@ -1,12 +1,12 @@
import { AbpVisibleDirective, NavItem, NavItemsService } from '@abp/ng.theme.shared';
import { Component, TrackByFunction } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CoreModule } from '@abp/ng.core';
import { PermissionDirective } from '@abp/ng.core';
@Component({
selector: 'abp-nav-items',
templateUrl: 'nav-items.component.html',
imports: [CommonModule, CoreModule, AbpVisibleDirective],
imports: [CommonModule, AbpVisibleDirective, PermissionDirective],
})
export class NavItemsComponent {
trackByFn: TrackByFunction<NavItem> = (_, element) => element.id;

4
npm/ng-packs/packages/theme-basic/src/lib/components/page-alert-container/page-alert-container.component.ts

@ -1,13 +1,13 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { PageAlertService } from '@abp/ng.theme.shared';
import { CommonModule } from '@angular/common';
import { CoreModule } from '@abp/ng.core';
import { LocalizationPipe, SafeHtmlPipe } from '@abp/ng.core';
@Component({
selector: 'abp-page-alert-container',
templateUrl: './page-alert-container.component.html',
encapsulation: ViewEncapsulation.None,
imports: [CommonModule, CoreModule],
imports: [CommonModule, LocalizationPipe, SafeHtmlPipe],
})
export class PageAlertContainerComponent {
constructor(public service: PageAlertService) {}

20
npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts

@ -1,4 +1,11 @@
import { ABP, CoreModule, RoutesService, TreeNode } from '@abp/ng.core';
import {
ABP,
LazyLocalizationPipe,
LocalizationPipe,
PermissionDirective,
RoutesService,
TreeNode,
} from '@abp/ng.core';
import {
Component,
ElementRef,
@ -12,11 +19,20 @@ import {
import { CommonModule } from '@angular/common';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { RouterModule } from '@angular/router';
import { EllipsisDirective } from '@abp/ng.theme.shared';
@Component({
selector: 'abp-routes',
templateUrl: 'routes.component.html',
imports: [CommonModule, RouterModule, CoreModule, NgbDropdownModule],
imports: [
CommonModule,
RouterModule,
NgbDropdownModule,
LazyLocalizationPipe,
PermissionDirective,
EllipsisDirective,
LocalizationPipe,
],
})
export class RoutesComponent {
public readonly routesService = inject(RoutesService);

6
npm/ng-packs/packages/theme-basic/src/lib/components/validation-error/validation-error.component.ts

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { Validation, ValidationErrorComponent as ErrorComponent } from '@ngx-validate/core';
import { CommonModule } from '@angular/common';
import { CoreModule } from '@abp/ng.core';
import { LocalizationPipe } from '@abp/ng.core';
@Component({
selector: 'abp-validation-error',
@ -14,7 +14,7 @@ import { CoreModule } from '@abp/ng.core';
`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
imports: [CommonModule, CoreModule],
imports: [CommonModule, LocalizationPipe],
})
export class ValidationErrorComponent extends ErrorComponent {
get abpErrors(): (Validation.Error & { interpoliteParams?: string[] })[] {

3
npm/ng-packs/packages/theme-shared/src/lib/components/password/password.component.ts

@ -2,6 +2,7 @@ import { Component, forwardRef, Input } from '@angular/core';
import { FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { AbstractNgModelComponent } from '@abp/ng.core';
import { NgxValidateCoreModule } from '@ngx-validate/core';
/**
* @deprecated use ShowPasswordDirective directive
@ -9,7 +10,7 @@ import { AbstractNgModelComponent } from '@abp/ng.core';
*/
@Component({
selector: 'abp-password',
imports: [CommonModule, FormsModule, ReactiveFormsModule],
imports: [CommonModule, FormsModule, ReactiveFormsModule, NgxValidateCoreModule],
templateUrl: `./password.component.html`,
providers: [
{

8
templates/app/angular/src/app/app.routes.ts

@ -8,20 +8,20 @@ export const appRoutes: Routes = [
},
{
path: 'account',
loadChildren: () => import('@abp/ng.account').then(m => m.AccountModule.forLazy()),
loadChildren: () => import('@abp/ng.account').then(m => m.createRoutes()),
},
{
path: 'identity',
loadChildren: () => import('@abp/ng.identity').then(m => m.IdentityModule.forLazy()),
loadChildren: () => import('@abp/ng.identity').then(m => m.createRoutes()),
},
{
path: 'tenant-management',
loadChildren: () =>
import('@abp/ng.tenant-management').then(m => m.TenantManagementModule.forLazy()),
import('@abp/ng.tenant-management').then(m => m.createRoutes()),
},
{
path: 'setting-management',
loadChildren: () =>
import('@abp/ng.setting-management').then(m => m.SettingManagementModule.forLazy()),
import('@abp/ng.setting-management').then(m => m.createRoutes()),
},
];

8
templates/module/angular/projects/dev-app/src/app/app.routes.ts

@ -8,21 +8,21 @@ export const appRoutes: Routes = [
},
{
path: 'account',
loadChildren: () => import('@abp/ng.account').then(m => m.AccountModule.forLazy()),
loadChildren: () => import('@abp/ng.account').then(m => m.createRoutes()),
},
{
path: 'identity',
loadChildren: () => import('@abp/ng.identity').then(m => m.IdentityModule.forLazy()),
loadChildren: () => import('@abp/ng.identity').then(m => m.createRoutes()),
},
{
path: 'tenant-management',
loadChildren: () =>
import('@abp/ng.tenant-management').then(m => m.TenantManagementModule.forLazy()),
import('@abp/ng.tenant-management').then(m => m.createRoutes()),
},
{
path: 'setting-management',
loadChildren: () =>
import('@abp/ng.setting-management').then(m => m.SettingManagementModule.forLazy()),
import('@abp/ng.setting-management').then(m => m.createRoutes()),
},
{
path: 'my-project-name',

Loading…
Cancel
Save